blob: 584d8fb311fa20ee490f1045c301dfc927ea1076 [file] [log] [blame]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "apr.h"
#include "apr_strings.h"
#include "apr_thread_proc.h"
#include "apr_hash.h"
#include "apr_user.h"
#include "apr_lib.h"
#include "apr_optional.h"
#define APR_WANT_STRFUNC
#define APR_WANT_MEMFUNC
#include "apr_want.h"
#include "ap_config.h"
#include "util_filter.h"
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"
#include "http_core.h"
#include "mod_include.h"
#include "ap_expr.h"
/* helper for Latin1 <-> entity encoding */
#if APR_CHARSET_EBCDIC
#include "util_ebcdic.h"
#define RAW_ASCII_CHAR(ch) apr_xlate_conv_byte(ap_hdrs_from_ascii, \
(unsigned char)ch)
#else /* APR_CHARSET_EBCDIC */
#define RAW_ASCII_CHAR(ch) (ch)
#endif /* !APR_CHARSET_EBCDIC */
/*
* +-------------------------------------------------------+
* | |
* | Types and Structures
* | |
* +-------------------------------------------------------+
*/
/* sll used for string expansion */
typedef struct result_item {
struct result_item *next;
apr_size_t len;
const char *string;
} result_item_t;
/* conditional expression parser stuff */
typedef enum {
TOKEN_STRING,
TOKEN_RE,
TOKEN_AND,
TOKEN_OR,
TOKEN_NOT,
TOKEN_EQ,
TOKEN_NE,
TOKEN_RBRACE,
TOKEN_LBRACE,
TOKEN_GROUP,
TOKEN_GE,
TOKEN_LE,
TOKEN_GT,
TOKEN_LT,
TOKEN_ACCESS
} token_type_t;
typedef struct {
token_type_t type;
const char *value;
#ifdef DEBUG_INCLUDE
const char *s;
#endif
} token_t;
typedef struct parse_node {
struct parse_node *parent;
struct parse_node *left;
struct parse_node *right;
token_t token;
int value;
int done;
#ifdef DEBUG_INCLUDE
int dump_done;
#endif
} parse_node_t;
typedef enum {
XBITHACK_OFF,
XBITHACK_ON,
XBITHACK_FULL,
XBITHACK_UNSET
} xbithack_t;
typedef struct {
const char *default_error_msg;
const char *default_time_fmt;
const char *undefined_echo;
xbithack_t xbithack;
signed char lastmodified;
signed char etag;
signed char legacy_expr;
} include_dir_config;
typedef struct {
const char *default_start_tag;
const char *default_end_tag;
} include_server_config;
/* main parser states */
typedef enum {
PARSE_PRE_HEAD,
PARSE_HEAD,
PARSE_DIRECTIVE,
PARSE_DIRECTIVE_POSTNAME,
PARSE_DIRECTIVE_TAIL,
PARSE_DIRECTIVE_POSTTAIL,
PARSE_PRE_ARG,
PARSE_ARG,
PARSE_ARG_NAME,
PARSE_ARG_POSTNAME,
PARSE_ARG_EQ,
PARSE_ARG_PREVAL,
PARSE_ARG_VAL,
PARSE_ARG_VAL_ESC,
PARSE_ARG_POSTVAL,
PARSE_TAIL,
PARSE_TAIL_SEQ,
PARSE_EXECUTE
} parse_state_t;
typedef struct arg_item {
struct arg_item *next;
char *name;
apr_size_t name_len;
char *value;
apr_size_t value_len;
} arg_item_t;
typedef struct {
const char *source;
const char *rexp;
apr_size_t nsub;
ap_regmatch_t match[AP_MAX_REG_MATCH];
int have_match;
} backref_t;
typedef struct {
unsigned int T[256];
unsigned int x;
apr_size_t pattern_len;
} bndm_t;
struct ssi_internal_ctx {
parse_state_t state;
int seen_eos;
int error;
char quote; /* quote character value (or \0) */
apr_size_t parse_pos; /* parse position of partial matches */
apr_size_t bytes_read;
apr_bucket_brigade *tmp_bb;
const char *start_seq;
bndm_t *start_seq_pat;
const char *end_seq;
apr_size_t end_seq_len;
char *directive; /* name of the current directive */
apr_size_t directive_len; /* length of the current directive name */
arg_item_t *current_arg; /* currently parsed argument */
arg_item_t *argv; /* all arguments */
backref_t *re; /* NULL if there wasn't a regex yet */
const char *undefined_echo;
apr_size_t undefined_echo_len;
char legacy_expr; /* use ap_expr or legacy mod_include
expression parser? */
ap_expr_eval_ctx_t *expr_eval_ctx; /* NULL if there wasn't an ap_expr yet */
const char *expr_vary_this; /* for use by ap_expr_eval_ctx */
const char *expr_err; /* for use by ap_expr_eval_ctx */
#ifdef DEBUG_INCLUDE
struct {
ap_filter_t *f;
apr_bucket_brigade *bb;
} debug;
#endif
};
/*
* +-------------------------------------------------------+
* | |
* | Debugging Utilities
* | |
* +-------------------------------------------------------+
*/
#ifdef DEBUG_INCLUDE
#define TYPE_TOKEN(token, ttype) do { \
(token)->type = ttype; \
(token)->s = #ttype; \
} while(0)
#define CREATE_NODE(ctx, name) do { \
(name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
(name)->parent = (name)->left = (name)->right = NULL; \
(name)->done = 0; \
(name)->dump_done = 0; \
} while(0)
static void debug_printf(include_ctx_t *ctx, const char *fmt, ...)
{
va_list ap;
char *debug__str;
va_start(ap, fmt);
debug__str = apr_pvsprintf(ctx->pool, fmt, ap);
va_end(ap);
APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
debug__str, strlen(debug__str), ctx->pool,
ctx->intern->debug.f->c->bucket_alloc));
}
#define DUMP__CHILD(ctx, is, node, child) if (1) { \
parse_node_t *d__c = node->child; \
if (d__c) { \
if (!d__c->dump_done) { \
if (d__c->parent != node) { \
debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
if (!d__c->parent) { \
debug_printf(ctx, "Parent of " #child " child node is " \
"NULL.\n"); \
} \
else { \
debug_printf(ctx, "Parent of " #child " child node " \
"points to another node (of type %s)!\n", \
d__c->parent->token.s); \
} \
return; \
} \
node = d__c; \
continue; \
} \
} \
else { \
debug_printf(ctx, "%s(missing)\n", is); \
} \
}
static void debug_dump_tree(include_ctx_t *ctx, parse_node_t *root)
{
parse_node_t *current;
char *is;
if (!root) {
debug_printf(ctx, " -- Parse Tree empty --\n\n");
return;
}
debug_printf(ctx, " ----- Parse Tree -----\n");
current = root;
is = " ";
while (current) {
switch (current->token.type) {
case TOKEN_STRING:
case TOKEN_RE:
debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
current->token.value);
current->dump_done = 1;
current = current->parent;
continue;
case TOKEN_NOT:
case TOKEN_GROUP:
case TOKEN_RBRACE:
case TOKEN_LBRACE:
if (!current->dump_done) {
debug_printf(ctx, "%s%s\n", is, current->token.s);
is = apr_pstrcat(ctx->dpool, is, " ", NULL);
current->dump_done = 1;
}
DUMP__CHILD(ctx, is, current, right)
if (!current->right || current->right->dump_done) {
is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
if (current->right) current->right->dump_done = 0;
current = current->parent;
}
continue;
default:
if (!current->dump_done) {
debug_printf(ctx, "%s%s\n", is, current->token.s);
is = apr_pstrcat(ctx->dpool, is, " ", NULL);
current->dump_done = 1;
}
DUMP__CHILD(ctx, is, current, left)
DUMP__CHILD(ctx, is, current, right)
if ((!current->left || current->left->dump_done) &&
(!current->right || current->right->dump_done)) {
is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
if (current->left) current->left->dump_done = 0;
if (current->right) current->right->dump_done = 0;
current = current->parent;
}
continue;
}
}
/* it is possible to call this function within the parser loop, to see
* how the tree is built. That way, we must cleanup after us to dump
* always the whole tree
*/
root->dump_done = 0;
if (root->left) root->left->dump_done = 0;
if (root->right) root->right->dump_done = 0;
debug_printf(ctx, " --- End Parse Tree ---\n\n");
}
#define DEBUG_INIT(ctx, filter, brigade) do { \
(ctx)->intern->debug.f = filter; \
(ctx)->intern->debug.bb = brigade; \
} while(0)
#define DEBUG_PRINTF(arg) debug_printf arg
#define DEBUG_DUMP_TOKEN(ctx, token) do { \
token_t *d__t = (token); \
\
if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
} \
else { \
DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
} \
} while(0)
#define DEBUG_DUMP_EVAL(ctx, node) do { \
char c = '"'; \
switch ((node)->token.type) { \
case TOKEN_STRING: \
debug_printf((ctx), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
(node)->token.value, ((node)->value) ? '1':'0'); \
break; \
case TOKEN_AND: \
case TOKEN_OR: \
debug_printf((ctx), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
(node)->token.s, \
(((node)->left->done) ? ((node)->left->value ?"1":"0") \
: "short circuited"), \
(((node)->right->done) ? ((node)->right->value?"1":"0") \
: "short circuited"), \
(node)->value ? '1' : '0'); \
break; \
case TOKEN_EQ: \
case TOKEN_NE: \
case TOKEN_GT: \
case TOKEN_GE: \
case TOKEN_LT: \
case TOKEN_LE: \
if ((node)->right->token.type == TOKEN_RE) c = '/'; \
debug_printf((ctx), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
(node)->token.s, \
(node)->left->token.value, \
c, (node)->right->token.value, c, \
(node)->value ? '1' : '0'); \
break; \
default: \
debug_printf((ctx), " Evaluate: %s -> %c\n", (node)->token.s, \
(node)->value ? '1' : '0'); \
break; \
} \
} while(0)
#define DEBUG_DUMP_UNMATCHED(ctx, unmatched) do { \
if (unmatched) { \
DEBUG_PRINTF(((ctx), " Unmatched %c\n", (char)(unmatched))); \
} \
} while(0)
#define DEBUG_DUMP_COND(ctx, text) \
DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
#define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
#else /* DEBUG_INCLUDE */
#define TYPE_TOKEN(token, ttype) (token)->type = ttype
#define CREATE_NODE(ctx, name) do { \
(name) = apr_palloc((ctx)->dpool, sizeof(*(name))); \
(name)->parent = (name)->left = (name)->right = NULL; \
(name)->done = 0; \
} while(0)
#define DEBUG_INIT(ctx, f, bb)
#define DEBUG_PRINTF(arg)
#define DEBUG_DUMP_TOKEN(ctx, token)
#define DEBUG_DUMP_EVAL(ctx, node)
#define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
#define DEBUG_DUMP_COND(ctx, text)
#define DEBUG_DUMP_TREE(ctx, root)
#endif /* !DEBUG_INCLUDE */
/*
* +-------------------------------------------------------+
* | |
* | Static Module Data
* | |
* +-------------------------------------------------------+
*/
/* global module structure */
module AP_MODULE_DECLARE_DATA include_module;
/* function handlers for include directives */
static apr_hash_t *include_handlers;
/* forward declaration of handler registry */
static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register;
/* Sentinel value to store in subprocess_env for items that
* shouldn't be evaluated until/unless they're actually used
*/
static const char lazy_eval_sentinel = '\0';
#define LAZY_VALUE (&lazy_eval_sentinel)
/* default values */
#define DEFAULT_START_SEQUENCE "<!--#"
#define DEFAULT_END_SEQUENCE "-->"
#define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
#define DEFAULT_UNDEFINED_ECHO "(none)"
#define UNSET -1
#ifdef XBITHACK
#define DEFAULT_XBITHACK XBITHACK_FULL
#else
#define DEFAULT_XBITHACK XBITHACK_OFF
#endif
/*
* +-------------------------------------------------------+
* | |
* | Environment/Expansion Functions
* | |
* +-------------------------------------------------------+
*/
/*
* decodes a string containing html entities or numeric character references.
* 's' is overwritten with the decoded string.
* If 's' is syntatically incorrect, then the followed fixups will be made:
* unknown entities will be left undecoded;
* references to unused numeric characters will be deleted.
* In particular, &#00; will not be decoded, but will be deleted.
*/
/* maximum length of any ISO-LATIN-1 HTML entity name. */
#define MAXENTLEN (6)
/* The following is a shrinking transformation, therefore safe. */
/* Note: this function is deprecated in favour of apr_unescape_entity() in APR */
static void decodehtml(char *s)
{
int val, i, j;
char *p;
const char *ents;
static const char * const entlist[MAXENTLEN + 1] =
{
NULL, /* 0 */
NULL, /* 1 */
"lt\074gt\076", /* 2 */
"amp\046ETH\320eth\360", /* 3 */
"quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml"
"\353iuml\357ouml\366uuml\374yuml\377", /* 4 */
"Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc"
"\333THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352"
"icirc\356ocirc\364ucirc\373thorn\376", /* 5 */
"Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311"
"Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde"
"\325Oslash\330Ugrave\331Uacute\332Yacute\335agrave\340"
"aacute\341atilde\343ccedil\347egrave\350eacute\351igrave"
"\354iacute\355ntilde\361ograve\362oacute\363otilde\365"
"oslash\370ugrave\371uacute\372yacute\375" /* 6 */
};
/* Do a fast scan through the string until we find anything
* that needs more complicated handling
*/
for (; *s != '&'; s++) {
if (*s == '\0') {
return;
}
}
for (p = s; *s != '\0'; s++, p++) {
if (*s != '&') {
*p = *s;
continue;
}
/* find end of entity */
for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
continue;
}
if (s[i] == '\0') { /* treat as normal data */
*p = *s;
continue;
}
/* is it numeric ? */
if (s[1] == '#') {
for (j = 2, val = 0; j < i && apr_isdigit(s[j]); j++) {
val = val * 10 + s[j] - '0';
}
s += i;
if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
(val >= 127 && val <= 160) || val >= 256) {
p--; /* no data to output */
}
else {
*p = RAW_ASCII_CHAR(val);
}
}
else {
j = i - 1;
if (j > MAXENTLEN || entlist[j] == NULL) {
/* wrong length */
*p = '&';
continue; /* skip it */
}
for (ents = entlist[j]; *ents != '\0'; ents += i) {
if (strncmp(s + 1, ents, j) == 0) {
break;
}
}
if (*ents == '\0') {
*p = '&'; /* unknown */
}
else {
*p = RAW_ASCII_CHAR(((const unsigned char *) ents)[j]);
s += i;
}
}
}
*p = '\0';
}
static void add_include_vars(request_rec *r)
{
apr_table_t *e = r->subprocess_env;
char *t;
apr_table_setn(e, "DATE_LOCAL", LAZY_VALUE);
apr_table_setn(e, "DATE_GMT", LAZY_VALUE);
apr_table_setn(e, "LAST_MODIFIED", LAZY_VALUE);
apr_table_setn(e, "DOCUMENT_URI", r->uri);
apr_table_setn(e, "DOCUMENT_ARGS", r->args ? r->args : "");
if (r->path_info && *r->path_info) {
apr_table_setn(e, "DOCUMENT_PATH_INFO", r->path_info);
}
apr_table_setn(e, "USER_NAME", LAZY_VALUE);
if (r->filename && (t = strrchr(r->filename, '/'))) {
apr_table_setn(e, "DOCUMENT_NAME", ++t);
}
else {
apr_table_setn(e, "DOCUMENT_NAME", r->uri);
}
if (r->args) {
char *arg_copy = apr_pstrdup(r->pool, r->args);
ap_unescape_url(arg_copy);
apr_table_setn(e, "QUERY_STRING_UNESCAPED",
ap_escape_shell_cmd(r->pool, arg_copy));
}
}
static const char *add_include_vars_lazy(request_rec *r, const char *var, const char *timefmt)
{
char *val;
if (!strcasecmp(var, "DATE_LOCAL")) {
val = ap_ht_time(r->pool, r->request_time, timefmt, 0);
}
else if (!strcasecmp(var, "DATE_GMT")) {
val = ap_ht_time(r->pool, r->request_time, timefmt, 1);
}
else if (!strcasecmp(var, "LAST_MODIFIED")) {
val = ap_ht_time(r->pool, r->finfo.mtime, timefmt, 0);
}
else if (!strcasecmp(var, "USER_NAME")) {
if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) {
val = "<unknown>";
}
}
else {
val = NULL;
}
if (val) {
apr_table_setn(r->subprocess_env, var, val);
}
return val;
}
static const char *get_include_var(const char *var, include_ctx_t *ctx)
{
const char *val;
request_rec *r = ctx->r;
if (apr_isdigit(*var) && !var[1]) {
apr_size_t idx = *var - '0';
backref_t *re = ctx->intern->re;
/* Handle $0 .. $9 from the last regex evaluated.
* The choice of returning NULL strings on not-found,
* v.s. empty strings on an empty match is deliberate.
*/
if (!re || !re->have_match) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01329)
"regex capture $%" APR_SIZE_T_FMT " refers to no regex in %s",
idx, r->filename);
return NULL;
}
else if (re->nsub < idx || idx >= AP_MAX_REG_MATCH) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01330)
"regex capture $%" APR_SIZE_T_FMT
" is out of range (last regex was: '%s') in %s",
idx, re->rexp, r->filename);
return NULL;
}
else if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) {
/* This particular subpattern was not used by the regex */
return NULL;
}
else {
val = apr_pstrmemdup(ctx->dpool, re->source + re->match[idx].rm_so,
re->match[idx].rm_eo - re->match[idx].rm_so);
}
}
else {
val = apr_table_get(r->subprocess_env, var);
if (val == LAZY_VALUE) {
val = add_include_vars_lazy(r, var, ctx->time_str);
}
}
return val;
}
static const char *include_expr_var_fn(ap_expr_eval_ctx_t *eval_ctx,
const void *data,
const char *arg)
{
const char *res, *name = data;
include_ctx_t *ctx = eval_ctx->data;
if ((name[0] == 'e') || (name[0] == 'E')) {
/* keep legacy "env" semantics */
if ((res = apr_table_get(ctx->r->notes, arg)) != NULL)
return res;
else if ((res = get_include_var(arg, ctx)) != NULL)
return res;
else
return getenv(arg);
}
else {
return get_include_var(arg, ctx);
}
}
static int include_expr_lookup(ap_expr_lookup_parms *parms)
{
switch (parms->type) {
case AP_EXPR_FUNC_STRING:
if (strcasecmp(parms->name, "v") == 0 ||
strcasecmp(parms->name, "reqenv") == 0 ||
strcasecmp(parms->name, "env") == 0) {
*parms->func = include_expr_var_fn;
*parms->data = parms->name;
return OK;
}
break;
/*
* We could also make the SSI vars available as %{...} style variables
* (AP_EXPR_FUNC_VAR), but this would create problems if we ever want
* to cache parsed expressions for performance reasons.
*/
}
return ap_run_expr_lookup(parms);
}
/*
* Do variable substitution on strings
*
* (Note: If out==NULL, this function allocs a buffer for the resulting
* string from ctx->pool. The return value is always the parsed string)
*/
static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out,
apr_size_t length, int leave_name)
{
request_rec *r = ctx->r;
result_item_t *result = NULL, *current = NULL;
apr_size_t outlen = 0, inlen, span;
char *ret = NULL, *eout = NULL;
const char *p;
if (out) {
/* sanity check, out && !length is not supported */
ap_assert(out && length);
ret = out;
eout = out + length - 1;
}
span = strcspn(in, "\\$");
inlen = strlen(in);
/* fast exit */
if (inlen == span) {
if (out) {
apr_cpystrn(out, in, length);
}
else {
ret = apr_pstrmemdup(ctx->pool, in, (length && length <= inlen)
? length - 1 : inlen);
}
return ret;
}
/* well, actually something to do */
p = in + span;
if (out) {
if (span) {
memcpy(out, in, (out+span <= eout) ? span : (eout-out));
out += span;
}
}
else {
current = result = apr_palloc(ctx->dpool, sizeof(*result));
current->next = NULL;
current->string = in;
current->len = span;
outlen = span;
}
/* loop for specials */
do {
if ((out && out >= eout) || (length && outlen >= length)) {
break;
}
/* prepare next entry */
if (!out && current->len) {
current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
current = current->next;
current->next = NULL;
current->len = 0;
}
/*
* escaped character
*/
if (*p == '\\') {
if (out) {
*out++ = (p[1] == '$') ? *++p : *p;
++p;
}
else {
current->len = 1;
current->string = (p[1] == '$') ? ++p : p;
++p;
++outlen;
}
}
/*
* variable expansion
*/
else { /* *p == '$' */
const char *newp = NULL, *ep, *key = NULL;
if (*++p == '{') {
ep = ap_strchr_c(++p, '}');
if (!ep) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01331) "Missing '}' on "
"variable \"%s\" in %s", p, r->filename);
break;
}
if (p < ep) {
key = apr_pstrmemdup(ctx->dpool, p, ep - p);
newp = ep + 1;
}
p -= 2;
}
else {
ep = p;
while (*ep == '_' || apr_isalnum(*ep)) {
++ep;
}
if (p < ep) {
key = apr_pstrmemdup(ctx->dpool, p, ep - p);
newp = ep;
}
--p;
}
/* empty name results in a copy of '$' in the output string */
if (!key) {
if (out) {
*out++ = *p++;
}
else {
current->len = 1;
current->string = p++;
++outlen;
}
}
else {
const char *val = get_include_var(key, ctx);
apr_size_t len = 0;
if (val) {
len = strlen(val);
}
else if (leave_name) {
val = p;
len = ep - p;
}
if (val && len) {
if (out) {
memcpy(out, val, (out+len <= eout) ? len : (eout-out));
out += len;
}
else {
current->len = len;
current->string = val;
outlen += len;
}
}
p = newp;
}
}
if ((out && out >= eout) || (length && outlen >= length)) {
break;
}
/* check the remainder */
if (*p && (span = strcspn(p, "\\$")) > 0) {
if (!out && current->len) {
current->next = apr_palloc(ctx->dpool, sizeof(*current->next));
current = current->next;
current->next = NULL;
}
if (out) {
memcpy(out, p, (out+span <= eout) ? span : (eout-out));
out += span;
}
else {
current->len = span;
current->string = p;
outlen += span;
}
p += span;
}
} while (p < in+inlen);
/* assemble result */
if (out) {
if (out > eout) {
*eout = '\0';
}
else {
*out = '\0';
}
}
else {
const char *ep;
if (length && outlen > length) {
outlen = length - 1;
}
ret = out = apr_palloc(ctx->pool, outlen + 1);
ep = ret + outlen;
do {
if (result->len) {
memcpy(out, result->string, (out+result->len <= ep)
? result->len : (ep-out));
out += result->len;
}
result = result->next;
} while (result && out < ep);
ret[outlen] = '\0';
}
return ret;
}
/*
* +-------------------------------------------------------+
* | |
* | Conditional Expression Parser
* | |
* +-------------------------------------------------------+
*/
static APR_INLINE int re_check(include_ctx_t *ctx, const char *string,
const char *rexp)
{
ap_regex_t *compiled;
backref_t *re = ctx->intern->re;
compiled = ap_pregcomp(ctx->dpool, rexp, AP_REG_EXTENDED);
if (!compiled) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(02667)
"unable to compile pattern \"%s\"", rexp);
return -1;
}
if (!re) {
re = ctx->intern->re = apr_palloc(ctx->pool, sizeof(*re));
}
re->source = apr_pstrdup(ctx->pool, string);
re->rexp = apr_pstrdup(ctx->pool, rexp);
re->nsub = compiled->re_nsub;
re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH,
re->match, 0);
ap_pregfree(ctx->dpool, compiled);
return re->have_match;
}
static int get_ptoken(include_ctx_t *ctx, const char **parse, token_t *token, token_t *previous)
{
const char *p;
apr_size_t shift;
int unmatched;
token->value = NULL;
if (!*parse) {
return 0;
}
/* Skip leading white space */
while (apr_isspace(**parse)) {
++*parse;
}
if (!**parse) {
*parse = NULL;
return 0;
}
TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
p = *parse;
unmatched = 0;
switch (*(*parse)++) {
case '(':
TYPE_TOKEN(token, TOKEN_LBRACE);
return 0;
case ')':
TYPE_TOKEN(token, TOKEN_RBRACE);
return 0;
case '=':
if (**parse == '=') ++*parse;
TYPE_TOKEN(token, TOKEN_EQ);
return 0;
case '!':
if (**parse == '=') {
TYPE_TOKEN(token, TOKEN_NE);
++*parse;
return 0;
}
TYPE_TOKEN(token, TOKEN_NOT);
return 0;
case '\'':
unmatched = '\'';
break;
case '/':
/* if last token was ACCESS, this token is STRING */
if (previous != NULL && TOKEN_ACCESS == previous->type) {
break;
}
TYPE_TOKEN(token, TOKEN_RE);
unmatched = '/';
break;
case '|':
if (**parse == '|') {
TYPE_TOKEN(token, TOKEN_OR);
++*parse;
return 0;
}
break;
case '&':
if (**parse == '&') {
TYPE_TOKEN(token, TOKEN_AND);
++*parse;
return 0;
}
break;
case '>':
if (**parse == '=') {
TYPE_TOKEN(token, TOKEN_GE);
++*parse;
return 0;
}
TYPE_TOKEN(token, TOKEN_GT);
return 0;
case '<':
if (**parse == '=') {
TYPE_TOKEN(token, TOKEN_LE);
++*parse;
return 0;
}
TYPE_TOKEN(token, TOKEN_LT);
return 0;
case '-':
if (**parse == 'A') {
TYPE_TOKEN(token, TOKEN_ACCESS);
++*parse;
return 0;
}
break;
}
/* It's a string or regex token
* Now search for the next token, which finishes this string
*/
shift = 0;
p = *parse = token->value = unmatched ? *parse : p;
for (; **parse; p = ++*parse) {
if (**parse == '\\') {
if (!*(++*parse)) {
p = *parse;
break;
}
++shift;
}
else {
if (unmatched) {
if (**parse == unmatched) {
unmatched = 0;
++*parse;
break;
}
} else if (apr_isspace(**parse)) {
break;
}
else {
int found = 0;
switch (**parse) {
case '(':
case ')':
case '=':
case '!':
case '<':
case '>':
++found;
break;
case '|':
case '&':
if ((*parse)[1] == **parse) {
++found;
}
break;
}
if (found) {
break;
}
}
}
}
if (unmatched) {
token->value = apr_pstrdup(ctx->dpool, "");
}
else {
apr_size_t len = p - token->value - shift;
char *c = apr_palloc(ctx->dpool, len + 1);
p = token->value;
token->value = c;
while (shift--) {
const char *e = ap_strchr_c(p, '\\');
memcpy(c, p, e-p);
c += e-p;
*c++ = *++e;
len -= e-p;
p = e+1;
}
if (len) {
memcpy(c, p, len);
}
c[len] = '\0';
}
return unmatched;
}
static int parse_expr(include_ctx_t *ctx, const char *expr, int *was_error)
{
parse_node_t *new, *root = NULL, *current = NULL;
request_rec *r = ctx->r;
request_rec *rr = NULL;
const char *error = APLOGNO(03188) "Invalid expression \"%s\" in file %s";
const char *parse = expr;
unsigned regex = 0;
*was_error = 0;
if (!parse) {
return 0;
}
/* Create Parse Tree */
while (1) {
/* uncomment this to see how the tree a built:
*
* DEBUG_DUMP_TREE(ctx, root);
*/
CREATE_NODE(ctx, new);
{
#ifdef DEBUG_INCLUDE
int was_unmatched =
#endif
get_ptoken(ctx, &parse, &new->token,
(current != NULL ? &current->token : NULL));
if (!parse)
break;
DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
DEBUG_DUMP_TOKEN(ctx, &new->token);
}
if (!current) {
switch (new->token.type) {
case TOKEN_STRING:
case TOKEN_NOT:
case TOKEN_ACCESS:
case TOKEN_LBRACE:
root = current = new;
continue;
default:
/* Intentional no APLOGNO */
/* error text provides APLOGNO */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr,
r->filename);
*was_error = 1;
return 0;
}
}
switch (new->token.type) {
case TOKEN_STRING:
switch (current->token.type) {
case TOKEN_STRING:
current->token.value =
apr_pstrcat(ctx->dpool, current->token.value,
*current->token.value ? " " : "",
new->token.value, NULL);
continue;
case TOKEN_RE:
case TOKEN_RBRACE:
case TOKEN_GROUP:
break;
default:
new->parent = current;
current = current->right = new;
continue;
}
break;
case TOKEN_RE:
switch (current->token.type) {
case TOKEN_EQ:
case TOKEN_NE:
new->parent = current;
current = current->right = new;
++regex;
continue;
default:
break;
}
break;
case TOKEN_AND:
case TOKEN_OR:
switch (current->token.type) {
case TOKEN_STRING:
case TOKEN_RE:
case TOKEN_GROUP:
current = current->parent;
while (current) {
switch (current->token.type) {
case TOKEN_AND:
case TOKEN_OR:
case TOKEN_LBRACE:
break;
default:
current = current->parent;
continue;
}
break;
}
if (!current) {
new->left = root;
root->parent = new;
current = root = new;
continue;
}
new->left = current->right;
new->left->parent = new;
new->parent = current;
current = current->right = new;
continue;
default:
break;
}
break;
case TOKEN_EQ:
case TOKEN_NE:
case TOKEN_GE:
case TOKEN_GT:
case TOKEN_LE:
case TOKEN_LT:
if (current->token.type == TOKEN_STRING) {
current = current->parent;
if (!current) {
new->left = root;
root->parent = new;
current = root = new;
continue;
}
switch (current->token.type) {
case TOKEN_LBRACE:
case TOKEN_AND:
case TOKEN_OR:
new->left = current->right;
new->left->parent = new;
new->parent = current;
current = current->right = new;
continue;
default:
break;
}
}
break;
case TOKEN_RBRACE:
while (current && current->token.type != TOKEN_LBRACE) {
current = current->parent;
}
if (current) {
TYPE_TOKEN(&current->token, TOKEN_GROUP);
continue;
}
error = APLOGNO(03189) "Unmatched ')' in \"%s\" in file %s";
break;
case TOKEN_NOT:
case TOKEN_ACCESS:
case TOKEN_LBRACE:
switch (current->token.type) {
case TOKEN_STRING:
case TOKEN_RE:
case TOKEN_RBRACE:
case TOKEN_GROUP:
break;
default:
current->right = new;
new->parent = current;
current = new;
continue;
}
break;
default:
break;
}
/* Intentional no APLOGNO */
/* error text provides APLOGNO */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
*was_error = 1;
return 0;
}
DEBUG_DUMP_TREE(ctx, root);
/* Evaluate Parse Tree */
current = root;
error = NULL;
while (current) {
switch (current->token.type) {
case TOKEN_STRING:
current->token.value =
ap_ssi_parse_string(ctx, current->token.value, NULL, 0,
SSI_EXPAND_DROP_NAME);
current->value = !!*current->token.value;
break;
case TOKEN_AND:
case TOKEN_OR:
if (!current->left || !current->right) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01332)
"Invalid expression \"%s\" in file %s",
expr, r->filename);
*was_error = 1;
return 0;
}
if (!current->left->done) {
switch (current->left->token.type) {
case TOKEN_STRING:
current->left->token.value =
ap_ssi_parse_string(ctx, current->left->token.value,
NULL, 0, SSI_EXPAND_DROP_NAME);
current->left->value = !!*current->left->token.value;
DEBUG_DUMP_EVAL(ctx, current->left);
current->left->done = 1;
break;
default:
current = current->left;
continue;
}
}
/* short circuit evaluation */
if (!current->right->done && !regex &&
((current->token.type == TOKEN_AND && !current->left->value) ||
(current->token.type == TOKEN_OR && current->left->value))) {
current->value = current->left->value;
}
else {
if (!current->right->done) {
switch (current->right->token.type) {
case TOKEN_STRING:
current->right->token.value =
ap_ssi_parse_string(ctx,current->right->token.value,
NULL, 0, SSI_EXPAND_DROP_NAME);
current->right->value = !!*current->right->token.value;
DEBUG_DUMP_EVAL(ctx, current->right);
current->right->done = 1;
break;
default:
current = current->right;
continue;
}
}
if (current->token.type == TOKEN_AND) {
current->value = current->left->value &&
current->right->value;
}
else {
current->value = current->left->value ||
current->right->value;
}
}
break;
case TOKEN_EQ:
case TOKEN_NE:
if (!current->left || !current->right ||
current->left->token.type != TOKEN_STRING ||
(current->right->token.type != TOKEN_STRING &&
current->right->token.type != TOKEN_RE)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01333)
"Invalid expression \"%s\" in file %s",
expr, r->filename);
*was_error = 1;
return 0;
}
current->left->token.value =
ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
SSI_EXPAND_DROP_NAME);
current->right->token.value =
ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
SSI_EXPAND_DROP_NAME);
if (current->right->token.type == TOKEN_RE) {
current->value = re_check(ctx, current->left->token.value,
current->right->token.value);
--regex;
}
else {
current->value = !strcmp(current->left->token.value,
current->right->token.value);
}
if (current->token.type == TOKEN_NE) {
current->value = !current->value;
}
break;
case TOKEN_GE:
case TOKEN_GT:
case TOKEN_LE:
case TOKEN_LT:
if (!current->left || !current->right ||
current->left->token.type != TOKEN_STRING ||
current->right->token.type != TOKEN_STRING) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01334)
"Invalid expression \"%s\" in file %s",
expr, r->filename);
*was_error = 1;
return 0;
}
current->left->token.value =
ap_ssi_parse_string(ctx, current->left->token.value, NULL, 0,
SSI_EXPAND_DROP_NAME);
current->right->token.value =
ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
SSI_EXPAND_DROP_NAME);
current->value = strcmp(current->left->token.value,
current->right->token.value);
switch (current->token.type) {
case TOKEN_GE: current->value = current->value >= 0; break;
case TOKEN_GT: current->value = current->value > 0; break;
case TOKEN_LE: current->value = current->value <= 0; break;
case TOKEN_LT: current->value = current->value < 0; break;
default: current->value = 0; break; /* should not happen */
}
break;
case TOKEN_NOT:
case TOKEN_GROUP:
if (current->right) {
if (!current->right->done) {
current = current->right;
continue;
}
current->value = current->right->value;
}
else {
current->value = 1;
}
if (current->token.type == TOKEN_NOT) {
current->value = !current->value;
}
break;
case TOKEN_ACCESS:
if (current->left || !current->right ||
(current->right->token.type != TOKEN_STRING &&
current->right->token.type != TOKEN_RE)) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01335)
"Invalid expression \"%s\" in file %s: Token '-A' must be followed by a URI string.",
expr, r->filename);
*was_error = 1;
return 0;
}
current->right->token.value =
ap_ssi_parse_string(ctx, current->right->token.value, NULL, 0,
SSI_EXPAND_DROP_NAME);
rr = ap_sub_req_lookup_uri(current->right->token.value, r, NULL);
/* 400 and higher are considered access denied */
if (rr->status < HTTP_BAD_REQUEST) {
current->value = 1;
}
else {
current->value = 0;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rr->status, r, APLOGNO(01336)
"mod_include: The tested "
"subrequest -A \"%s\" returned an error code.",
current->right->token.value);
}
ap_destroy_sub_req(rr);
break;
case TOKEN_RE:
if (!error) {
error = APLOGNO(03190) "No operator before regex in expr \"%s\" in file %s";
}
case TOKEN_LBRACE:
if (!error) {
error = APLOGNO(03191) "Unmatched '(' in \"%s\" in file %s";
}
default:
if (!error) {
error = APLOGNO(03192) "internal parser error in \"%s\" in file %s";
}
/* Intentional no APLOGNO */
/* error text provides APLOGNO */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename);
*was_error = 1;
return 0;
}
DEBUG_DUMP_EVAL(ctx, current);
current->done = 1;
current = current->parent;
}
return (root ? root->value : 0);
}
/* same as above, but use common ap_expr syntax / API */
static int parse_ap_expr(include_ctx_t *ctx, const char *expr, int *was_error)
{
ap_expr_info_t *expr_info = apr_pcalloc(ctx->pool, sizeof (*expr_info));
const char *err;
int ret;
backref_t *re = ctx->intern->re;
ap_expr_eval_ctx_t *eval_ctx = ctx->intern->expr_eval_ctx;
expr_info->filename = ctx->r->filename;
expr_info->line_number = 0;
expr_info->module_index = APLOG_MODULE_INDEX;
expr_info->flags = AP_EXPR_FLAG_RESTRICTED;
err = ap_expr_parse(ctx->r->pool, ctx->r->pool, expr_info, expr,
include_expr_lookup);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01337)
"Could not parse expr \"%s\" in %s: %s", expr,
ctx->r->filename, err);
*was_error = 1;
return 0;
}
if (!re) {
ctx->intern->re = re = apr_pcalloc(ctx->pool, sizeof(*re));
}
else {
/* ap_expr_exec_ctx() does not care about re->have_match but only about
* re->source
*/
if (!re->have_match)
re->source = NULL;
}
if (!eval_ctx) {
eval_ctx = apr_pcalloc(ctx->pool, sizeof(*eval_ctx));
ctx->intern->expr_eval_ctx = eval_ctx;
eval_ctx->r = ctx->r;
eval_ctx->c = ctx->r->connection;
eval_ctx->s = ctx->r->server;
eval_ctx->p = ctx->r->pool;
eval_ctx->data = ctx;
eval_ctx->err = &ctx->intern->expr_err;
eval_ctx->vary_this = &ctx->intern->expr_vary_this;
eval_ctx->re_nmatch = AP_MAX_REG_MATCH;
eval_ctx->re_pmatch = re->match;
eval_ctx->re_source = &re->source;
}
eval_ctx->info = expr_info;
ret = ap_expr_exec_ctx(eval_ctx);
if (ret < 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01338)
"Could not evaluate expr \"%s\" in %s: %s", expr,
ctx->r->filename, ctx->intern->expr_err);
*was_error = 1;
return 0;
}
*was_error = 0;
if (re->source)
re->have_match = 1;
return ret;
}
/*
* +-------------------------------------------------------+
* | |
* | Action Handlers
* | |
* +-------------------------------------------------------+
*/
/*
* Extract the next tag name and value.
* If there are no more tags, set the tag name to NULL.
* The tag value is html decoded if dodecode is non-zero.
* The tag value may be NULL if there is no tag value..
*/
static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag,
char **tag_val, int dodecode)
{
if (!ctx->intern->argv) {
*tag = NULL;
*tag_val = NULL;
return;
}
*tag_val = ctx->intern->argv->value;
*tag = ctx->intern->argv->name;
ctx->intern->argv = ctx->intern->argv->next;
if (dodecode && *tag_val) {
decodehtml(*tag_val);
}
}
static int find_file(request_rec *r, const char *directive, const char *tag,
char *tag_val, apr_finfo_t *finfo)
{
char *to_send = tag_val;
request_rec *rr = NULL;
int ret=0;
char *error_fmt = NULL;
apr_status_t rv = APR_SUCCESS;
if (!strcmp(tag, "file")) {
char *newpath;
/* be safe; only files in this directory or below allowed */
rv = apr_filepath_merge(&newpath, NULL, tag_val,
APR_FILEPATH_SECUREROOTTEST |
APR_FILEPATH_NOTABSOLUTE, r->pool);
if (rv != APR_SUCCESS) {
error_fmt = APLOGNO(02668) "unable to access file \"%s\" "
"in parsed file %s";
}
else {
/* note: it is okay to pass NULL for the "next filter" since
we never attempt to "run" this sub request. */
rr = ap_sub_req_lookup_file(newpath, r, NULL);
if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
to_send = rr->filename;
if ((rv = apr_stat(finfo, to_send,
APR_FINFO_GPROT | APR_FINFO_MIN, rr->pool)) != APR_SUCCESS
&& rv != APR_INCOMPLETE) {
error_fmt = APLOGNO(02669) "unable to get information "
"about \"%s\" in parsed file %s";
}
}
else {
error_fmt = APLOGNO(02670) "unable to lookup information "
"about \"%s\" in parsed file %s";
}
}
if (error_fmt) {
ret = -1;
/* Intentional no APLOGNO */
/* error_fmt provides APLOGNO */
ap_log_rerror(APLOG_MARK, APLOG_ERR,
rv, r, error_fmt, to_send, r->filename);
}
if (rr) ap_destroy_sub_req(rr);
return ret;
}
else if (!strcmp(tag, "virtual")) {
/* note: it is okay to pass NULL for the "next filter" since
we never attempt to "run" this sub request. */
rr = ap_sub_req_lookup_uri(tag_val, r, NULL);
if (rr->status == HTTP_OK && rr->finfo.filetype != APR_NOFILE) {
memcpy((char *) finfo, (const char *) &rr->finfo,
sizeof(rr->finfo));
ap_destroy_sub_req(rr);
return 0;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01339) "unable to get "
"information about \"%s\" in parsed file %s",
tag_val, r->filename);
ap_destroy_sub_req(rr);
return -1;
}
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01340) "unknown parameter \"%s\" "
"to tag %s in %s", tag, directive, r->filename);
return -1;
}
}
/*
* <!--#comment blah blah blah ... -->
*/
static apr_status_t handle_comment(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
return APR_SUCCESS;
}
/*
* <!--#include virtual|file="..." [onerror|virtual|file="..."] ... -->
*
* Output each file/virtual in turn until one of them returns an error.
* On error, ignore all further file/virtual attributes until we reach
* an onerror attribute, where we make an attempt to serve the onerror
* virtual url. If onerror fails, or no onerror is present, the default
* error string is inserted into the stream.
*/
static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
char *last_error;
if (!ctx->argc) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01341)
"missing argument for include element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (!ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
last_error = NULL;
while (1) {
char *tag = NULL;
char *tag_val = NULL;
request_rec *rr = NULL;
char *error_fmt = NULL;
char *parsed_string;
apr_status_t rv = APR_SUCCESS;
int status = 0;
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
if (!tag || !tag_val) {
break;
}
if (strcmp(tag, "virtual") && strcmp(tag, "file") && strcmp(tag,
"onerror")) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01342) "unknown parameter "
"\"%s\" to tag include in %s", tag, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
if (tag[0] == 'f') {
char *newpath;
/* be safe; only files in this directory or below allowed */
rv = apr_filepath_merge(&newpath, NULL, parsed_string,
APR_FILEPATH_SECUREROOTTEST |
APR_FILEPATH_NOTABSOLUTE, ctx->dpool);
if (rv != APR_SUCCESS) {
error_fmt = "unable to include file \"%s\" in parsed file %s";
}
else {
rr = ap_sub_req_lookup_file(newpath, r, f->next);
}
}
else if ((tag[0] == 'v' && !last_error)
|| (tag[0] == 'o' && last_error)) {
if (r->kept_body) {
rr = ap_sub_req_method_uri(r->method, parsed_string, r, f->next);
}
else {
rr = ap_sub_req_lookup_uri(parsed_string, r, f->next);
}
}
else {
continue;
}
if (!error_fmt && rr->status != HTTP_OK) {
error_fmt = "unable to include \"%s\" in parsed file %s, subrequest setup returned %d";
}
if (!error_fmt && (ctx->flags & SSI_FLAG_NO_EXEC) &&
rr->content_type && strncmp(rr->content_type, "text/", 5)) {
error_fmt = "unable to include potential exec \"%s\" in parsed "
"file %s, content type not text/*";
}
/* See the Kludge in includes_filter for why.
* Basically, it puts a bread crumb in here, then looks
* for the crumb later to see if its been here.
*/
if (rr) {
ap_set_module_config(rr->request_config, &include_module, r);
}
if (!error_fmt && ((status = ap_run_sub_req(rr)))) {
error_fmt = "unable to include \"%s\" in parsed file %s, subrequest returned %d";
}
if (error_fmt) {
/* Intentional no APLOGNO */
/* error text is also sent to client */
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, error_fmt, tag_val,
r->filename, status ? status : rr ? rr->status : 0);
if (last_error) {
/* onerror threw an error, give up completely */
break;
}
last_error = error_fmt;
}
else {
last_error = NULL;
}
/* Do *not* destroy the subrequest here; it may have allocated
* variables in this r->subprocess_env in the subrequest's
* r->pool, so that pool must survive as long as this request.
* Yes, this is a memory leak. */
}
if (last_error) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
}
return APR_SUCCESS;
}
/*
* <!--#echo [decoding="..."] [encoding="..."] var="..." [decoding="..."]
* [encoding="..."] var="..." ... -->
*/
static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
const char *encoding = "entity", *decoding = "none";
request_rec *r = f->r;
int error = 0;
if (!ctx->argc) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01343)
"missing argument for echo element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (!ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
while (1) {
char *tag = NULL;
char *tag_val = NULL;
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
if (!tag || !tag_val) {
break;
}
if (!strcmp(tag, "var")) {
const char *val;
const char *echo_text = NULL;
apr_size_t e_len;
val = get_include_var(ap_ssi_parse_string(ctx, tag_val, NULL,
0, SSI_EXPAND_DROP_NAME),
ctx);
if (val) {
char *last = NULL;
char *e, *d, *token;
echo_text = val;
d = apr_pstrdup(ctx->pool, decoding);
token = apr_strtok(d, ", \t", &last);
while (token) {
if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
else if (!ap_cstr_casecmp(token, "url")) {
char *buf = apr_pstrdup(ctx->pool, echo_text);
ap_unescape_url(buf);
echo_text = buf;
}
else if (!ap_cstr_casecmp(token, "urlencoded")) {
char *buf = apr_pstrdup(ctx->pool, echo_text);
ap_unescape_urlencoded(buf);
echo_text = buf;
}
else if (!ap_cstr_casecmp(token, "entity")) {
char *buf = apr_pstrdup(ctx->pool, echo_text);
decodehtml(buf);
echo_text = buf;
}
else if (!ap_cstr_casecmp(token, "base64")) {
echo_text = ap_pbase64decode(ctx->dpool, echo_text);
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01344) "unknown value "
"\"%s\" to parameter \"decoding\" of tag echo in "
"%s", token, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
error = 1;
break;
}
token = apr_strtok(NULL, ", \t", &last);
}
e = apr_pstrdup(ctx->pool, encoding);
token = apr_strtok(e, ", \t", &last);
while (token) {
if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
else if (!ap_cstr_casecmp(token, "url")) {
echo_text = ap_escape_uri(ctx->dpool, echo_text);
}
else if (!ap_cstr_casecmp(token, "urlencoded")) {
echo_text = ap_escape_urlencoded(ctx->dpool, echo_text);
}
else if (!ap_cstr_casecmp(token, "entity")) {
echo_text = ap_escape_html2(ctx->dpool, echo_text, 0);
}
else if (!ap_cstr_casecmp(token, "base64")) {
char *buf;
buf = ap_pbase64encode(ctx->dpool, (char *)echo_text);
echo_text = buf;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01345) "unknown value "
"\"%s\" to parameter \"encoding\" of tag echo in "
"%s", token, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
error = 1;
break;
}
token = apr_strtok(NULL, ", \t", &last);
}
e_len = strlen(echo_text);
}
else {
echo_text = ctx->intern->undefined_echo;
e_len = ctx->intern->undefined_echo_len;
}
if (error) {
break;
}
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(
apr_pmemdup(ctx->pool, echo_text, e_len),
e_len, ctx->pool, f->c->bucket_alloc));
}
else if (!strcmp(tag, "decoding")) {
decoding = tag_val;
}
else if (!strcmp(tag, "encoding")) {
encoding = tag_val;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01346) "unknown parameter "
"\"%s\" in tag echo of %s", tag, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
}
return APR_SUCCESS;
}
/*
* <!--#config [timefmt="..."] [sizefmt="..."] [errmsg="..."]
* [echomsg="..."] -->
*/
static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
apr_table_t *env = r->subprocess_env;
if (!ctx->argc) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01347)
"missing argument for config element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (!ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
while (1) {
char *tag = NULL;
char *tag_val = NULL;
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
if (!tag || !tag_val) {
break;
}
if (!strcmp(tag, "errmsg")) {
ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
}
else if (!strcmp(tag, "echomsg")) {
ctx->intern->undefined_echo =
ap_ssi_parse_string(ctx, tag_val, NULL, 0,SSI_EXPAND_DROP_NAME);
ctx->intern->undefined_echo_len=strlen(ctx->intern->undefined_echo);
}
else if (!strcmp(tag, "timefmt")) {
apr_time_t date = r->request_time;
ctx->time_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
apr_table_setn(env, "DATE_LOCAL", ap_ht_time(r->pool, date,
ctx->time_str, 0));
apr_table_setn(env, "DATE_GMT", ap_ht_time(r->pool, date,
ctx->time_str, 1));
apr_table_setn(env, "LAST_MODIFIED",
ap_ht_time(r->pool, r->finfo.mtime,
ctx->time_str, 0));
}
else if (!strcmp(tag, "sizefmt")) {
char *parsed_string;
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
if (!strcmp(parsed_string, "bytes")) {
ctx->flags |= SSI_FLAG_SIZE_IN_BYTES;
}
else if (!strcmp(parsed_string, "abbrev")) {
ctx->flags &= SSI_FLAG_SIZE_ABBREV;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01348) "unknown value "
"\"%s\" to parameter \"sizefmt\" of tag config "
"in %s", parsed_string, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01349) "unknown parameter "
"\"%s\" to tag config in %s", tag, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
}
return APR_SUCCESS;
}
/*
* <!--#fsize virtual|file="..." [virtual|file="..."] ... -->
*/
static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
if (!ctx->argc) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01350)
"missing argument for fsize element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (!ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
while (1) {
char *tag = NULL;
char *tag_val = NULL;
apr_finfo_t finfo;
char *parsed_string;
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
if (!tag || !tag_val) {
break;
}
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
if (!find_file(r, "fsize", tag, parsed_string, &finfo)) {
char *buf;
apr_size_t len;
if (!(ctx->flags & SSI_FLAG_SIZE_IN_BYTES)) {
buf = apr_strfsize(finfo.size, apr_palloc(ctx->pool, 5));
len = 4; /* omit the \0 terminator */
}
else {
apr_size_t l, x, pos;
char *tmp;
tmp = apr_psprintf(ctx->dpool, "%" APR_OFF_T_FMT, finfo.size);
len = l = strlen(tmp);
for (x = 0; x < l; ++x) {
if (x && !((l - x) % 3)) {
++len;
}
}
if (len == l) {
buf = apr_pstrmemdup(ctx->pool, tmp, len);
}
else {
buf = apr_palloc(ctx->pool, len);
for (pos = x = 0; x < l; ++x) {
if (x && !((l - x) % 3)) {
buf[pos++] = ',';
}
buf[pos++] = tmp[x];
}
}
}
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(buf, len,
ctx->pool, f->c->bucket_alloc));
}
else {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
}
return APR_SUCCESS;
}
/*
* <!--#flastmod virtual|file="..." [virtual|file="..."] ... -->
*/
static apr_status_t handle_flastmod(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
if (!ctx->argc) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01351)
"missing argument for flastmod element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (!ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
while (1) {
char *tag = NULL;
char *tag_val = NULL;
apr_finfo_t finfo;
char *parsed_string;
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_DECODED);
if (!tag || !tag_val) {
break;
}
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) {
char *t_val;
apr_size_t t_len;
t_val = ap_ht_time(ctx->pool, finfo.mtime, ctx->time_str, 0);
t_len = strlen(t_val);
APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(t_val, t_len,
ctx->pool, f->c->bucket_alloc));
}
else {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
}
return APR_SUCCESS;
}
/*
* <!--#if expr="..." -->
*/
static apr_status_t handle_if(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
char *tag = NULL;
char *expr = NULL;
request_rec *r = f->r;
int expr_ret, was_error;
if (ctx->argc != 1) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r,
(ctx->argc)
? APLOGNO(01352) "too many arguments for if element in %s"
: APLOGNO(01353) "missing expr argument for if element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
++(ctx->if_nesting_level);
return APR_SUCCESS;
}
if (ctx->argc != 1) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
if (strcmp(tag, "expr")) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01354) "unknown parameter \"%s\" "
"to tag if in %s", tag, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
if (!expr) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01355) "missing expr value for if "
"element in %s", r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
DEBUG_PRINTF((ctx, "**** if expr=\"%s\"\n", expr));
if (ctx->intern->legacy_expr)
expr_ret = parse_expr(ctx, expr, &was_error);
else
expr_ret = parse_ap_expr(ctx, expr, &was_error);
if (was_error) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
if (expr_ret) {
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
}
else {
ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
}
DEBUG_DUMP_COND(ctx, " if");
ctx->if_nesting_level = 0;
return APR_SUCCESS;
}
/*
* <!--#elif expr="..." -->
*/
static apr_status_t handle_elif(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
char *tag = NULL;
char *expr = NULL;
request_rec *r = f->r;
int expr_ret, was_error;
if (ctx->argc != 1) {
ap_log_rerror(APLOG_MARK,
(!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
0, r,
(ctx->argc)
? APLOGNO(01356) "too many arguments for if element in %s"
: APLOGNO(01357) "missing expr argument for if element in %s",
r->filename);
}
if (ctx->if_nesting_level) {
return APR_SUCCESS;
}
if (ctx->argc != 1) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
ap_ssi_get_tag_and_value(ctx, &tag, &expr, SSI_VALUE_RAW);
if (strcmp(tag, "expr")) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01358) "unknown parameter \"%s\" "
"to tag if in %s", tag, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
if (!expr) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01359) "missing expr in elif "
"statement: %s", r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
DEBUG_PRINTF((ctx, "**** elif expr=\"%s\"\n", expr));
DEBUG_DUMP_COND(ctx, " elif");
if (ctx->flags & SSI_FLAG_COND_TRUE) {
ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
return APR_SUCCESS;
}
if (ctx->intern->legacy_expr)
expr_ret = parse_expr(ctx, expr, &was_error);
else
expr_ret = parse_ap_expr(ctx, expr, &was_error);
if (was_error) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
if (expr_ret) {
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
}
else {
ctx->flags &= SSI_FLAG_CLEAR_PRINT_COND;
}
DEBUG_DUMP_COND(ctx, " elif");
return APR_SUCCESS;
}
/*
* <!--#else -->
*/
static apr_status_t handle_else(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
if (ctx->argc) {
ap_log_rerror(APLOG_MARK,
(!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01360)
"else directive does not take tags in %s",
r->filename);
}
if (ctx->if_nesting_level) {
return APR_SUCCESS;
}
if (ctx->argc) {
if (ctx->flags & SSI_FLAG_PRINTING) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
}
return APR_SUCCESS;
}
DEBUG_DUMP_COND(ctx, " else");
if (ctx->flags & SSI_FLAG_COND_TRUE) {
ctx->flags &= SSI_FLAG_CLEAR_PRINTING;
}
else {
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
}
return APR_SUCCESS;
}
/*
* <!--#endif -->
*/
static apr_status_t handle_endif(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
if (ctx->argc) {
ap_log_rerror(APLOG_MARK,
(!(ctx->if_nesting_level)) ? APLOG_ERR : APLOG_WARNING,
0, r, APLOGNO(01361)
"endif directive does not take tags in %s",
r->filename);
}
if (ctx->if_nesting_level) {
--(ctx->if_nesting_level);
return APR_SUCCESS;
}
if (ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
DEBUG_DUMP_COND(ctx, "endif");
ctx->flags |= (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
return APR_SUCCESS;
}
/*
* <!--#set var="..." value="..." ... -->
*/
static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
const char *encoding = "none", *decoding = "none";
char *var = NULL;
request_rec *r = f->r;
request_rec *sub = r->main;
apr_pool_t *p = r->pool;
int error = 0;
if (ctx->argc < 2) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r,
APLOGNO(01362) "missing argument for set element in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (ctx->argc < 2) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
/* we need to use the 'main' request pool to set notes as that is
* a notes lifetime
*/
while (sub) {
p = sub->pool;
sub = sub->main;
}
while (1) {
char *tag = NULL;
char *tag_val = NULL;
ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW);
if (!tag || !tag_val) {
break;
}
if (!strcmp(tag, "var")) {
decodehtml(tag_val);
var = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
}
else if (!strcmp(tag, "decoding")) {
decoding = tag_val;
}
else if (!strcmp(tag, "encoding")) {
encoding = tag_val;
}
else if (!strcmp(tag, "value")) {
char *parsed_string;
if (!var) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01363) "variable must "
"precede value in set directive in %s",
r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0,
SSI_EXPAND_DROP_NAME);
if (parsed_string) {
char *last = NULL;
char *e, *d, *token;
d = apr_pstrdup(ctx->pool, decoding);
token = apr_strtok(d, ", \t", &last);
while (token) {
if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
else if (!ap_cstr_casecmp(token, "url")) {
char *buf = apr_pstrdup(ctx->pool, parsed_string);
ap_unescape_url(buf);
parsed_string = buf;
}
else if (!ap_cstr_casecmp(token, "urlencoded")) {
char *buf = apr_pstrdup(ctx->pool, parsed_string);
ap_unescape_urlencoded(buf);
parsed_string = buf;
}
else if (!ap_cstr_casecmp(token, "entity")) {
char *buf = apr_pstrdup(ctx->pool, parsed_string);
decodehtml(buf);
parsed_string = buf;
}
else if (!ap_cstr_casecmp(token, "base64")) {
parsed_string = ap_pbase64decode(ctx->dpool, parsed_string);
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01364) "unknown value "
"\"%s\" to parameter \"decoding\" of tag set in "
"%s", token, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
error = 1;
break;
}
token = apr_strtok(NULL, ", \t", &last);
}
e = apr_pstrdup(ctx->pool, encoding);
token = apr_strtok(e, ", \t", &last);
while (token) {
if (!ap_cstr_casecmp(token, "none")) {
/* do nothing */
}
else if (!ap_cstr_casecmp(token, "url")) {
parsed_string = ap_escape_uri(ctx->dpool, parsed_string);
}
else if (!ap_cstr_casecmp(token, "urlencoded")) {
parsed_string = ap_escape_urlencoded(ctx->dpool, parsed_string);
}
else if (!ap_cstr_casecmp(token, "entity")) {
parsed_string = ap_escape_html2(ctx->dpool, parsed_string, 0);
}
else if (!ap_cstr_casecmp(token, "base64")) {
char *buf;
buf = ap_pbase64encode(ctx->dpool, (char *)parsed_string);
parsed_string = buf;
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01365) "unknown value "
"\"%s\" to parameter \"encoding\" of tag set in "
"%s", token, r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
error = 1;
break;
}
token = apr_strtok(NULL, ", \t", &last);
}
}
if (error) {
break;
}
apr_table_setn(r->subprocess_env, apr_pstrdup(p, var),
apr_pstrdup(p, parsed_string));
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01366) "Invalid tag for set "
"directive in %s", r->filename);
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
break;
}
}
return APR_SUCCESS;
}
/*
* <!--#printenv -->
*/
static apr_status_t handle_printenv(include_ctx_t *ctx, ap_filter_t *f,
apr_bucket_brigade *bb)
{
request_rec *r = f->r;
const apr_array_header_t *arr;
const apr_table_entry_t *elts;
int i;
if (ctx->argc) {
ap_log_rerror(APLOG_MARK,
(ctx->flags & SSI_FLAG_PRINTING)
? APLOG_ERR : APLOG_WARNING,
0, r,
APLOGNO(01367) "printenv directive does not take tags in %s",
r->filename);
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
return APR_SUCCESS;
}
if (ctx->argc) {
SSI_CREATE_ERROR_BUCKET(ctx, f, bb);
return APR_SUCCESS;
}
arr = apr_table_elts(r->subprocess_env);
elts = (apr_table_entry_t *)arr->elts;
for (i = 0; i < arr->nelts; ++i) {
const char *key_text, *val_text;
/* get key */
key_text = ap_escape_html(ctx->dpool, elts[i].key);
/* get value */
val_text = elts[i].val;
if (val_text == LAZY_VALUE)
val_text = add_include_vars_lazy(r, elts[i].key, ctx->time_str);
val_text = ap_escape_html(ctx->dpool, val_text);
apr_brigade_putstrs(bb, NULL, NULL, key_text, "=", val_text, "\n",
NULL);
}
ctx->flush_now = 1;
return APR_SUCCESS;
}
/*
* +-------------------------------------------------------+
* | |
* | Main Includes-Filter Engine
* | |
* +-------------------------------------------------------+
*/
/* This is an implementation of the BNDM search algorithm.
*
* Fast and Flexible String Matching by Combining Bit-parallelism and
* Suffix Automata (2001)
* Gonzalo Navarro, Mathieu Raffinot
*
* http://www-igm.univ-mlv.fr/~raffinot/ftp/jea2001.ps.gz
*
* Initial code submitted by Sascha Schumann.
*/
/* Precompile the bndm_t data structure. */
static bndm_t *bndm_compile(apr_pool_t *pool, const char *n, apr_size_t nl)
{
unsigned int x;
const char *ne = n + nl;
bndm_t *t = apr_palloc(pool, sizeof(*t));
memset(t->T, 0, sizeof(unsigned int) * 256);
t->pattern_len = nl;
for (x = 1; n < ne; x <<= 1) {
t->T[(unsigned char) *n++] |= x;
}
t->x = x - 1;
return t;
}
/* Implements the BNDM search algorithm (as described above).
*
* h - the string to look in
* hl - length of the string to look for
* t - precompiled bndm structure against the pattern
*
* Returns the count of character that is the first match or hl if no
* match is found.
*/
static apr_size_t bndm(bndm_t *t, const char *h, apr_size_t hl)
{
const char *skip;
const char *he, *p, *pi;
unsigned int *T, x, d;
apr_size_t nl;
he = h + hl;
T = t->T;
x = t->x;
nl = t->pattern_len;
pi = h - 1; /* pi: p initial */
p = pi + nl; /* compare window right to left. point to the first char */
while (p < he) {
skip = p;
d = x;
do {
d &= T[(unsigned char) *p--];
if (!d) {
break;
}
if ((d & 1)) {
if (p != pi) {
skip = p;
}
else {
return p - h + 1;
}
}
d >>= 1;
} while (d);
pi = skip;
p = pi + nl;
}
return hl;
}
/*
* returns the index position of the first byte of start_seq (or the len of
* the buffer as non-match)
*/
static apr_size_t find_start_sequence(include_ctx_t *ctx, const char *data,
apr_size_t len)
{
struct ssi_internal_ctx *intern = ctx->intern;
apr_size_t slen = intern->start_seq_pat->pattern_len;
apr_size_t index;
const char *p, *ep;
if (len < slen) {
p = data; /* try partial match at the end of the buffer (below) */
}
else {
/* try fast bndm search over the buffer
* (hopefully the whole start sequence can be found in this buffer)
*/
index = bndm(intern->start_seq_pat, data, len);
/* wow, found it. ready. */
if (index < len) {
intern->state = PARSE_DIRECTIVE;
return index;
}
else {
/* ok, the pattern can't be found as whole in the buffer,
* check the end for a partial match
*/
p = data + len - slen + 1;
}
}
ep = data + len;
do {
while (p < ep && *p != *intern->start_seq) {
++p;
}
index = p - data;
/* found a possible start_seq start */
if (p < ep) {
apr_size_t pos = 1;
++p;
while (p < ep && *p == intern->start_seq[pos]) {
++p;
++pos;
}
/* partial match found. Store the info for the next round */
if (p == ep) {
intern->state = PARSE_HEAD;
intern->parse_pos = pos;
return index;
}
}
/* we must try all combinations; consider (e.g.) SSIStartTag "--->"
* and a string data of "--.-" and the end of the buffer
*/
p = data + index + 1;
} while (p < ep);
/* no match */
return len;
}
/*
* returns the first byte *after* the partial (or final) match.
*
* If we had to trick with the start_seq start, 'release' returns the
* number of chars of the start_seq which appeared not to be part of a
* full tag and may have to be passed down the filter chain.
*/
static apr_size_t find_partial_start_sequence(include_ctx_t *ctx,
const char *data,
apr_size_t len,
apr_size_t *release)
{
struct ssi_internal_ctx *intern = ctx->intern;
apr_size_t pos, spos = 0;
apr_size_t slen = intern->start_seq_pat->pattern_len;
const char *p, *ep;
pos = intern->parse_pos;
ep = data + len;
*release = 0;
do {
p = data;
while (p < ep && pos < slen && *p == intern->start_seq[pos]) {
++p;
++pos;
}
/* full match */
if (pos == slen) {
intern->state = PARSE_DIRECTIVE;
return (p - data);
}
/* the whole buffer is a partial match */
if (p == ep) {
intern->parse_pos = pos;
return (p - data);
}
/* No match so far, but again:
* We must try all combinations, since the start_seq is a random
* user supplied string
*
* So: look if the first char of start_seq appears somewhere within
* the current partial match. If it does, try to start a match that
* begins with this offset. (This can happen, if a strange
* start_seq like "---->" spans buffers)
*/
if (spos < intern->parse_pos) {
do {
++spos;
++*release;
p = intern->start_seq + spos;
pos = intern->parse_pos - spos;
while (pos && *p != *intern->start_seq) {
++p;
++spos;
++*release;
--pos;
}
/* if a matching beginning char was found, try to match the
* remainder of the old buffer.
*/
if (pos > 1) {
apr_size_t t = 1;
++p;
while (t < pos && *p == intern->start_seq[t]) {
++p;
++t;
}
if (t == pos) {
/* yeah, another partial match found in the *old*
* buffer, now test the *current* buffer for
* continuing match
*/
break;
}
}
} while (pos > 1);
if (pos) {
continue;
}
}
break;
} while (1); /* work hard to find a match ;-) */
/* no match at all, release all (wrongly) matched chars so far */
*release = intern->parse_pos;
intern->state = PARSE_PRE_HEAD;
return 0;
}
/*
* returns the position after the directive
*/
static apr_size_t find_directive(include_ctx_t *ctx, const char *data,
apr_size_t len, char ***store,
apr_size_t **store_len)
{
struct ssi_internal_ctx *intern = ctx->intern;
const char *p = data;
const char *ep = data + len;
apr_size_t pos;
switch (intern->state) {
case PARSE_DIRECTIVE:
while (p < ep && !apr_isspace(*p)) {
/* we have to consider the case of missing space between directive
* and end_seq (be somewhat lenient), e.g. <!--#printenv-->
*/
if (*p == *intern->end_seq) {
intern->state = PARSE_DIRECTIVE_TAIL;
intern->parse_pos = 1;
++p;
return (p - data);
}
++p;
}
if (p < ep) { /* found delimiter whitespace */
intern->state = PARSE_DIRECTIVE_POSTNAME;
*store = &intern->directive;
*store_len = &intern->directive_len;
}
break;
case PARSE_DIRECTIVE_TAIL:
pos = intern->parse_pos;
while (p < ep && pos < intern->end_seq_len &&
*p == intern->end_seq[pos]) {
++p;
++pos;
}
/* full match, we're done */
if (pos == intern->end_seq_len) {
intern->state = PARSE_DIRECTIVE_POSTTAIL;
*store = &intern->directive;
*store_len = &intern->directive_len;
break;
}
/* partial match, the buffer is too small to match fully */
if (p == ep) {
intern->parse_pos = pos;
break;
}
/* no match. continue normal parsing */
intern->state = PARSE_DIRECTIVE;
return 0;
case PARSE_DIRECTIVE_POSTTAIL:
intern->state = PARSE_EXECUTE;
intern->directive_len -= intern->end_seq_len;
/* continue immediately with the next state */
case PARSE_DIRECTIVE_POSTNAME:
if (PARSE_DIRECTIVE_POSTNAME == intern->state) {
intern->state = PARSE_PRE_ARG;
}
ctx->argc = 0;
intern->argv = NULL;
if (!intern->directive_len) {
intern->error = 1;
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01368) "missing "
"directive name in parsed document %s",
ctx->r->filename);
}
else {
char *sp = intern->directive;
char *sep = intern->directive + intern->directive_len;
/* normalize directive name */
for (; sp < sep; ++sp) {
*sp = apr_tolower(*sp);
}
}
return 0;
default:
/* get a rid of a gcc warning about unhandled enumerations */
break;
}
return (p - data);
}
/*
* find out whether the next token is (a possible) end_seq or an argument
*/
static apr_size_t find_arg_or_tail(include_ctx_t *ctx, const char *data,
apr_size_t len)
{
struct ssi_internal_ctx *intern = ctx->intern;
const char *p = data;
const char *ep = data + len;
/* skip leading WS */
while (p < ep && apr_isspace(*p)) {
++p;
}
/* buffer doesn't consist of whitespaces only */
if (p < ep) {
intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG;
}
return (p - data);
}
/*
* test the stream for end_seq. If it doesn't match at all, it must be an
* argument
*/
static apr_size_t find_tail(include_ctx_t *ctx, const char *data,
apr_size_t len)
{
struct ssi_internal_ctx *intern = ctx->intern;
const char *p = data;
const char *ep = data + len;
apr_size_t pos = intern->parse_pos;
if (PARSE_TAIL == intern->state) {
intern->state = PARSE_TAIL_SEQ;
pos = intern->parse_pos = 0;
}
while (p < ep && pos < intern->end_seq_len && *p == intern->end_seq[pos]) {
++p;
++pos;
}
/* bingo, full match */
if (pos == intern->end_seq_len) {
intern->state = PARSE_EXECUTE;
return (p - data);
}
/* partial match, the buffer is too small to match fully */
if (p == ep) {
intern->parse_pos = pos;
return (p - data);
}
/* no match. It must be an argument string then
* The caller should cleanup and rewind to the reparse point
*/
intern->state = PARSE_ARG;
return 0;
}
/*
* extract name=value from the buffer
* A pcre-pattern could look (similar to):
* name\s*(?:=\s*(["'`]?)value\1(?>\s*))?
*/
static apr_size_t find_argument(include_ctx_t *ctx, const char *data,
apr_size_t len, char ***store,
apr_size_t **store_len)
{
struct ssi_internal_ctx *intern = ctx->intern;
const char *p = data;
const char *ep = data + len;
switch (intern->state) {
case PARSE_ARG:
/*
* create argument structure and append it to the current list
*/
intern->current_arg = apr_palloc(ctx->dpool,
sizeof(*intern->current_arg));
intern->current_arg->next = NULL;
++(ctx->argc);
if (!intern->argv) {
intern->argv = intern->current_arg;
}
else {
arg_item_t *newarg = intern->argv;
while (newarg->next) {
newarg = newarg->next;
}
newarg->next = intern->current_arg;
}
/* check whether it's a valid one. If it begins with a quote, we
* can safely assume, someone forgot the name of the argument
*/
switch (*p) {
case '"': case '\'': case '`':
*store = NULL;
intern->state = PARSE_ARG_VAL;
intern->quote = *p++;
intern->current_arg->name = NULL;
intern->current_arg->name_len = 0;
intern->error = 1;
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01369) "missing "
"argument name for value to tag %s in %s",
apr_pstrmemdup(ctx->r->pool, intern->directive,
intern->directive_len),
ctx->r->filename);
return (p - data);
default:
intern->state = PARSE_ARG_NAME;
}
/* continue immediately with next state */
case PARSE_ARG_NAME:
while (p < ep && !apr_isspace(*p) && *p != '=') {
++p;
}
if (p < ep) {
intern->state = PARSE_ARG_POSTNAME;
*store = &intern->current_arg->name;
*store_len = &intern->current_arg->name_len;
return (p - data);
}
break;
case PARSE_ARG_POSTNAME:
intern->current_arg->name = apr_pstrmemdup(ctx->dpool,
intern->current_arg->name,
intern->current_arg->name_len);
if (!intern->current_arg->name_len) {
intern->error = 1;
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r, APLOGNO(01370) "missing "
"argument name for value to tag %s in %s",
apr_pstrmemdup(ctx->r->pool, intern->directive,
intern->directive_len),
ctx->r->filename);
}
else {
ap_str_tolower(intern->current_arg->name);
}
intern->state = PARSE_ARG_EQ;
/* continue with next state immediately */
case PARSE_ARG_EQ:
*store = NULL;
while (p < ep && apr_isspace(*p)) {
++p;
}
if (p < ep) {
if (*p == '=') {
intern->state = PARSE_ARG_PREVAL;
++p;
}
else { /* no value */
intern->current_arg->value = NULL;
intern->state = PARSE_PRE_ARG;
}
return (p - data);
}
break;
case PARSE_ARG_PREVAL:
*store = NULL;
while (p < ep && apr_isspace(*p)) {
++p;
}
/* buffer doesn't consist of whitespaces only */
if (p < ep) {
intern->state = PARSE_ARG_VAL;
switch (*p) {
case '"': case '\'': case '`':
intern->quote = *p++;
break;
default:
intern->quote = '\0';
break;
}
return (p - data);
}
break;
case PARSE_ARG_VAL_ESC:
if (*p == intern->quote) {
++p;
}
intern->state = PARSE_ARG_VAL;
/* continue with next state immediately */
case PARSE_ARG_VAL:
for (; p < ep; ++p) {
if (intern->quote && *p == '\\') {
++p;
if (p == ep) {
intern->state = PARSE_ARG_VAL_ESC;
break;
}
if (*p != intern->quote) {
--p;
}
}
else if (intern->quote && *p == intern->quote) {
++p;
*store = &intern->current_arg->value;
*store_len = &intern->current_arg->value_len;
intern->state = PARSE_ARG_POSTVAL;
break;
}
else if (!intern->quote && apr_isspace(*p)) {
++p;
*store = &intern->current_arg->value;
*store_len = &intern->current_arg->value_len;
intern->state = PARSE_ARG_POSTVAL;
break;
}
}
return (p - data);
case PARSE_ARG_POSTVAL:
/*
* The value is still the raw input string. Finally clean it up.
*/
--(intern->current_arg->value_len);
/* strip quote escaping \ from the string */
if (intern->quote) {
apr_size_t shift = 0;
char *sp;
sp = intern->current_arg->value;
ep = intern->current_arg->value + intern->current_arg->value_len;
while (sp < ep && *sp != '\\') {
++sp;
}
for (; sp < ep; ++sp) {
if (*sp == '\\' && sp[1] == intern->quote) {
++sp;
++shift;
}
if (shift) {
*(sp-shift) = *sp;
}
}
intern->current_arg->value_len -= shift;
}
intern->current_arg->value[intern->current_arg->value_len] = '\0';
intern->state = PARSE_PRE_ARG;
return 0;
default:
/* get a rid of a gcc warning about unhandled enumerations */
break;
}
return len; /* partial match of something */
}
/*
* This is the main loop over the current bucket brigade.
*/
static apr_status_t send_parsed_content(ap_filter_t *f, apr_bucket_brigade *bb)
{
include_ctx_t *ctx = f->ctx;
struct ssi_internal_ctx *intern = ctx->intern;
request_rec *r = f->r;
apr_bucket *b = APR_BRIGADE_FIRST(bb);
apr_bucket_brigade *pass_bb;
apr_status_t rv = APR_SUCCESS;
char *magic; /* magic pointer for sentinel use */
/* fast exit */
if (APR_BRIGADE_EMPTY(bb)) {
return APR_SUCCESS;
}
/* we may crash, since already cleaned up; hand over the responsibility
* to the next filter;-)
*/
if (intern->seen_eos) {
return ap_pass_brigade(f->next, bb);
}
/* All stuff passed along has to be put into that brigade */
pass_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
/* initialization for this loop */
intern->bytes_read = 0;
intern->error = 0;
ctx->flush_now = 0;
/* loop over the current bucket brigade */
while (b != APR_BRIGADE_SENTINEL(bb)) {
const char *data = NULL;
apr_size_t len, index, release;
apr_bucket *newb = NULL;
char **store = &magic;
apr_size_t *store_len = NULL;
/* handle meta buckets before reading any data */
if (APR_BUCKET_IS_METADATA(b)) {
newb = APR_BUCKET_NEXT(b);
APR_BUCKET_REMOVE(b);
if (APR_BUCKET_IS_EOS(b)) {
intern->seen_eos = 1;
/* Hit end of stream, time for cleanup ... But wait!
* Perhaps we're not ready yet. We may have to loop one or
* two times again to finish our work. In that case, we
* just re-insert the EOS bucket to allow for an extra loop.
*
* PARSE_EXECUTE means, we've hit a directive just before the
* EOS, which is now waiting for execution.
*
* PARSE_DIRECTIVE_POSTTAIL means, we've hit a directive with
* no argument and no space between directive and end_seq
* just before the EOS. (consider <!--#printenv--> as last
* or only string within the stream). This state, however,
* just cleans up and turns itself to PARSE_EXECUTE, which
* will be passed through within the next (and actually
* last) round.
*/
if (PARSE_EXECUTE == intern->state ||
PARSE_DIRECTIVE_POSTTAIL == intern->state) {
APR_BUCKET_INSERT_BEFORE(newb, b);
}
else {
break; /* END OF STREAM */
}
}
else {
APR_BRIGADE_INSERT_TAIL(pass_bb, b);
if (APR_BUCKET_IS_FLUSH(b)) {
ctx->flush_now = 1;
}
b = newb;
continue;
}
}
/* enough is enough ... */
if (ctx->flush_now ||
intern->bytes_read > AP_MIN_BYTES_TO_WRITE) {
if (!APR_BRIGADE_EMPTY(pass_bb)) {
rv = ap_pass_brigade(f->next, pass_bb);
if (rv != APR_SUCCESS) {
apr_brigade_destroy(pass_bb);
return rv;
}
}
ctx->flush_now = 0;
intern->bytes_read = 0;
}
/* read the current bucket data */
len = 0;
if (!intern->seen_eos) {
if (intern->bytes_read > 0) {
rv = apr_bucket_read(b, &data, &len, APR_NONBLOCK_READ);
if (APR_STATUS_IS_EAGAIN(rv)) {
ctx->flush_now = 1;
continue;
}
}
if (!len || rv != APR_SUCCESS) {
rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
}
if (rv != APR_SUCCESS) {
apr_brigade_destroy(pass_bb);
return rv;
}
intern->bytes_read += len;
}
/* zero length bucket, fetch next one */
if (!len && !intern->seen_eos) {
b = APR_BUCKET_NEXT(b);
continue;
}
/*
* it's actually a data containing bucket, start/continue parsing
*/
switch (intern->state) {
/* no current tag; search for start sequence */
case PARSE_PRE_HEAD:
index = find_start_sequence(ctx, data, len);
if (index < len) {
apr_bucket_split(b, index);
}
newb = APR_BUCKET_NEXT(b);
if (ctx->flags & SSI_FLAG_PRINTING) {
APR_BUCKET_REMOVE(b);
APR_BRIGADE_INSERT_TAIL(pass_bb, b);
}
else {
apr_bucket_delete(b);
}
if (index < len) {
/* now delete the start_seq stuff from the remaining bucket */
if (PARSE_DIRECTIVE == intern->state) { /* full match */
apr_bucket_split(newb, intern->start_seq_pat->pattern_len);
ctx->flush_now = 1; /* pass pre-tag stuff */
}
b = APR_BUCKET_NEXT(newb);
apr_bucket_delete(newb);
}
else {
b = newb;
}
break;
/* we're currently looking for the end of the start sequence */
case PARSE_HEAD:
index = find_partial_start_sequence(ctx, data, len, &release);
/* check if we mismatched earlier and have to release some chars */
if (release && (ctx->flags & SSI_FLAG_PRINTING)) {
char *to_release = apr_pmemdup(ctx->pool, intern->start_seq, release);
newb = apr_bucket_pool_create(to_release, release, ctx->pool,
f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(pass_bb, newb);
}
if (index) { /* any match */
/* now delete the start_seq stuff from the remaining bucket */
if (PARSE_DIRECTIVE == intern->state) { /* final match */
apr_bucket_split(b, index);
ctx->flush_now = 1; /* pass pre-tag stuff */
}
newb = APR_BUCKET_NEXT(b);
apr_bucket_delete(b);
b = newb;
}
break;
/* we're currently grabbing the directive name */
case PARSE_DIRECTIVE:
case PARSE_DIRECTIVE_POSTNAME:
case PARSE_DIRECTIVE_TAIL:
case PARSE_DIRECTIVE_POSTTAIL:
index = find_directive(ctx, data, len, &store, &store_len);
if (index) {
apr_bucket_split(b, index);
newb = APR_BUCKET_NEXT(b);
}
if (store) {
if (index) {
APR_BUCKET_REMOVE(b);
apr_bucket_setaside(b, r->pool);
APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
b = newb;
}
/* time for cleanup? */
if (store != &magic) {
apr_brigade_pflatten(intern->tmp_bb, store, store_len,
ctx->dpool);
apr_brigade_cleanup(intern->tmp_bb);
}
}
else if (index) {
apr_bucket_delete(b);
b = newb;
}
break;
/* skip WS and find out what comes next (arg or end_seq) */
case PARSE_PRE_ARG:
index = find_arg_or_tail(ctx, data, len);
if (index) { /* skipped whitespaces */
if (index < len) {
apr_bucket_split(b, index);
}
newb = APR_BUCKET_NEXT(b);
apr_bucket_delete(b);
b = newb;
}
break;
/* currently parsing name[=val] */
case PARSE_ARG:
case PARSE_ARG_NAME:
case PARSE_ARG_POSTNAME:
case PARSE_ARG_EQ:
case PARSE_ARG_PREVAL:
case PARSE_ARG_VAL:
case PARSE_ARG_VAL_ESC:
case PARSE_ARG_POSTVAL:
index = find_argument(ctx, data, len, &store, &store_len);
if (index) {
apr_bucket_split(b, index);
newb = APR_BUCKET_NEXT(b);
}
if (store) {
if (index) {
APR_BUCKET_REMOVE(b);
apr_bucket_setaside(b, r->pool);
APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
b = newb;
}
/* time for cleanup? */
if (store != &magic) {
apr_brigade_pflatten(intern->tmp_bb, store, store_len,
ctx->dpool);
apr_brigade_cleanup(intern->tmp_bb);
}
}
else if (index) {
apr_bucket_delete(b);
b = newb;
}
break;
/* try to match end_seq at current pos. */
case PARSE_TAIL:
case PARSE_TAIL_SEQ:
index = find_tail(ctx, data, len);
switch (intern->state) {
case PARSE_EXECUTE: /* full match */
apr_bucket_split(b, index);
newb = APR_BUCKET_NEXT(b);
apr_bucket_delete(b);
b = newb;
break;
case PARSE_ARG: /* no match */
/* PARSE_ARG must reparse at the beginning */
APR_BRIGADE_PREPEND(bb, intern->tmp_bb);
b = APR_BRIGADE_FIRST(bb);
break;
default: /* partial match */
newb = APR_BUCKET_NEXT(b);
APR_BUCKET_REMOVE(b);
apr_bucket_setaside(b, r->pool);
APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b);
b = newb;
break;
}
break;
/* now execute the parsed directive, cleanup the space and
* start again with PARSE_PRE_HEAD
*/
case PARSE_EXECUTE:
/* if there was an error, it was already logged; just stop here */
if (intern->error) {
if (ctx->flags & SSI_FLAG_PRINTING) {
SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
intern->error = 0;
}
}
else {
include_handler_fn_t *handle_func;
handle_func =
(include_handler_fn_t *)apr_hash_get(include_handlers, intern->directive,
intern->directive_len);
if (handle_func) {
DEBUG_INIT(ctx, f, pass_bb);
rv = handle_func(ctx, f, pass_bb);
if (rv != APR_SUCCESS) {
apr_brigade_destroy(pass_bb);
return rv;
}
}
else {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01371)
"unknown directive \"%s\" in parsed doc %s",
apr_pstrmemdup(r->pool, intern->directive,
intern->directive_len),
r->filename);
if (ctx->flags & SSI_FLAG_PRINTING) {
SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
}
}
}
/* cleanup */
apr_pool_clear(ctx->dpool);
apr_brigade_cleanup(intern->tmp_bb);
/* Oooof. Done here, start next round */
intern->state = PARSE_PRE_HEAD;
break;
} /* switch(ctx->state) */
} /* while (brigade) */
/* End of stream. Final cleanup */
if (intern->seen_eos) {
if (PARSE_HEAD == intern->state) {
if (ctx->flags & SSI_FLAG_PRINTING) {
char *to_release = apr_pmemdup(ctx->pool, intern->start_seq,
intern->parse_pos);
APR_BRIGADE_INSERT_TAIL(pass_bb,
apr_bucket_pool_create(to_release,
intern->parse_pos, ctx->pool,
f->c->bucket_alloc));
}
}
else if (PARSE_PRE_HEAD != intern->state) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01372)
"SSI directive was not properly finished at the end "
"of parsed document %s", r->filename);
if (ctx->flags & SSI_FLAG_PRINTING) {
SSI_CREATE_ERROR_BUCKET(ctx, f, pass_bb);
}
}
if (!(ctx->flags & SSI_FLAG_PRINTING)) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01373)
"missing closing endif directive in parsed document"
" %s", r->filename);
}
/* cleanup our temporary memory */
apr_brigade_destroy(intern->tmp_bb);
apr_pool_destroy(ctx->dpool);
/* don't forget to finally insert the EOS bucket */
APR_BRIGADE_INSERT_TAIL(pass_bb, b);
}
/* if something's left over, pass it along */
if (!APR_BRIGADE_EMPTY(pass_bb)) {
rv = ap_pass_brigade(f->next, pass_bb);
}
else {
rv = APR_SUCCESS;
apr_brigade_destroy(pass_bb);
}
return rv;
}
/*
* +-------------------------------------------------------+
* | |
* | Runtime Hooks
* | |
* +-------------------------------------------------------+
*/
static int includes_setup(ap_filter_t *f)
{
include_dir_config *conf = ap_get_module_config(f->r->per_dir_config,
&include_module);
/* When our xbithack value isn't set to full or our platform isn't
* providing group-level protection bits or our group-level bits do not
* have group-execite on, we will set the no_local_copy value to 1 so
* that we will not send 304s.
*/
if ((conf->xbithack != XBITHACK_FULL)
|| !(f->r->finfo.valid & APR_FINFO_GPROT)
|| !(f->r->finfo.protection & APR_GEXECUTE)) {
f->r->no_local_copy = 1;
}
/* Don't allow ETag headers to be generated - see RFC2616 - 13.3.4.
* We don't know if we are going to be including a file or executing
* a program - in either case a strong ETag header will likely be invalid.
*/
if (conf->etag <= 0) {
apr_table_setn(f->r->notes, "no-etag", "");
}
return OK;
}
static apr_status_t includes_filter(ap_filter_t *f, apr_bucket_brigade *b)
{
request_rec *r = f->r;
request_rec *parent;
include_dir_config *conf = ap_get_module_config(r->per_dir_config,
&include_module);
include_server_config *sconf= ap_get_module_config(r->server->module_config,
&include_module);
if (!(ap_allow_options(r) & OPT_INCLUDES)) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01374)
"mod_include: Options +Includes (or IncludesNoExec) "
"wasn't set, INCLUDES filter removed: %s", r->uri);
ap_remove_output_filter(f);
return ap_pass_brigade(f->next, b);
}
if (!f->ctx) {
struct ssi_internal_ctx *intern;
include_ctx_t *ctx;
/* create context for this filter */
f->ctx = ctx = apr_palloc(r->pool, sizeof(*ctx));
ctx->r = r;
ctx->intern = intern = apr_palloc(r->pool, sizeof(*ctx->intern));
ctx->pool = r->pool;
apr_pool_create(&ctx->dpool, ctx->pool);
apr_pool_tag(ctx->dpool, "includes_dpool");
/* runtime data */
intern->tmp_bb = apr_brigade_create(ctx->pool, f->c->bucket_alloc);
intern->seen_eos = 0;
intern->state = PARSE_PRE_HEAD;
ctx->flags = (SSI_FLAG_PRINTING | SSI_FLAG_COND_TRUE);
if ((ap_allow_options(r) & OPT_INC_WITH_EXEC) == 0) {
ctx->flags |= SSI_FLAG_NO_EXEC;
}
intern->legacy_expr = (conf->legacy_expr > 0);
intern->expr_eval_ctx = NULL;
intern->expr_err = NULL;
intern->expr_vary_this = NULL;
ctx->if_nesting_level = 0;
intern->re = NULL;
ctx->error_str = conf->default_error_msg ? conf->default_error_msg :
DEFAULT_ERROR_MSG;
ctx->time_str = conf->default_time_fmt ? conf->default_time_fmt :
DEFAULT_TIME_FORMAT;
intern->start_seq = sconf->default_start_tag;
intern->start_seq_pat = bndm_compile(ctx->pool, intern->start_seq,
strlen(intern->start_seq));
intern->end_seq = sconf->default_end_tag;
intern->end_seq_len = strlen(intern->end_seq);
intern->undefined_echo = conf->undefined_echo ? conf->undefined_echo :
DEFAULT_UNDEFINED_ECHO;
intern->undefined_echo_len = strlen(intern->undefined_echo);
}
if ((parent = ap_get_module_config(r->request_config, &include_module))) {
/* Kludge --- for nested includes, we want to keep the subprocess
* environment of the base document (for compatibility); that means
* torquing our own last_modified date as well so that the
* LAST_MODIFIED variable gets reset to the proper value if the
* nested document resets <!--#config timefmt -->.
*/
r->subprocess_env = r->main->subprocess_env;
apr_pool_join(r->main->pool, r->pool);
r->finfo.mtime = r->main->finfo.mtime;
}
else {
/* we're not a nested include, so we create an initial
* environment */
ap_add_common_vars(r);
ap_add_cgi_vars(r);
add_include_vars(r);
}
/* Always unset the content-length. There is no way to know if
* the content will be modified at some point by send_parsed_content.
* It is very possible for us to not find any content in the first
* 9k of the file, but still have to modify the content of the file.
* If we are going to pass the file through send_parsed_content, then
* the content-length should just be unset.
*/
apr_table_unset(f->r->headers_out, "Content-Length");
/* Always unset the Last-Modified field - see RFC2616 - 13.3.4.
* We don't know if we are going to be including a file or executing
* a program which may change the Last-Modified header or make the
* content completely dynamic. Therefore, we can't support these
* headers.
*
* Exception: XBitHack full means we *should* set the
* Last-Modified field.
*
* SSILastModified on means we *should* set the Last-Modified field
* if not present, or respect an existing value if present.
*/
/* Must we respect the last modified header? By default, no */
if (conf->lastmodified > 0) {
/* update the last modified if we have a valid time, and only if
* we don't already have a valid last modified.
*/
if (r->finfo.valid & APR_FINFO_MTIME
&& !apr_table_get(f->r->headers_out, "Last-Modified")) {
ap_update_mtime(r, r->finfo.mtime);
ap_set_last_modified(r);
}
}
/* Assure the platform supports Group protections */
else if (((conf->xbithack == XBITHACK_FULL ||
(conf->xbithack == XBITHACK_UNSET &&
DEFAULT_XBITHACK == XBITHACK_FULL))
&& (r->finfo.valid & APR_FINFO_GPROT)
&& (r->finfo.protection & APR_GEXECUTE))) {
ap_update_mtime(r, r->finfo.mtime);
ap_set_last_modified(r);
}
else {
apr_table_unset(f->r->headers_out, "Last-Modified");
}
/* add QUERY stuff to env cause it ain't yet */
if (r->args) {
char *arg_copy = apr_pstrdup(r->pool, r->args);
apr_table_setn(r->subprocess_env, "QUERY_STRING", r->args);
ap_unescape_url(arg_copy);
apr_table_setn(r->subprocess_env, "QUERY_STRING_UNESCAPED",
ap_escape_shell_cmd(r->pool, arg_copy));
}
return send_parsed_content(f, b);
}
static int include_fixup(request_rec *r)
{
if (r->handler && (strcmp(r->handler, "server-parsed") == 0))
{
if (!r->content_type || !*r->content_type) {
ap_set_content_type(r, "text/html");
}
r->handler = "default-handler";
}
else
#if defined(OS2) || defined(WIN32) || defined(NETWARE)
/* These OS's don't support xbithack. This is being worked on. */
{
return DECLINED;
}
#else
{
include_dir_config *conf = ap_get_module_config(r->per_dir_config,
&include_module);
if (conf->xbithack == XBITHACK_OFF ||
(DEFAULT_XBITHACK == XBITHACK_OFF &&
conf->xbithack == XBITHACK_UNSET))
{
return DECLINED;
}
if (!(r->finfo.protection & APR_UEXECUTE)) {
return DECLINED;
}
if (!r->content_type || strncmp(r->content_type, "text/html", 9)) {
return DECLINED;
}
}
#endif
/* We always return declined, because the default handler actually
* serves the file. All we have to do is add the filter.
*/
ap_add_output_filter("INCLUDES", NULL, r, r->connection);
return DECLINED;
}
/*
* +-------------------------------------------------------+
* | |
* | Configuration Handling
* | |
* +-------------------------------------------------------+
*/
static void *create_includes_dir_config(apr_pool_t *p, char *dummy)
{
include_dir_config *result = apr_pcalloc(p, sizeof(include_dir_config));
result->xbithack = XBITHACK_UNSET;
result->lastmodified = UNSET;
result->etag = UNSET;
result->legacy_expr = UNSET;
return result;
}
#define MERGE(b,o,n,val,unset) n->val = o->val != unset ? o->val : b->val
static void *merge_includes_dir_config(apr_pool_t *p, void *basev, void *overridesv)
{
include_dir_config *base = (include_dir_config *)basev,
*over = (include_dir_config *)overridesv,
*new = apr_palloc(p, sizeof(include_dir_config));
MERGE(base, over, new, default_error_msg, NULL);
MERGE(base, over, new, default_time_fmt, NULL);
MERGE(base, over, new, undefined_echo, NULL);
MERGE(base, over, new, xbithack, XBITHACK_UNSET);
MERGE(base, over, new, lastmodified, UNSET);
MERGE(base, over, new, etag, UNSET);
MERGE(base, over, new, legacy_expr, UNSET);
return new;
}
static void *create_includes_server_config(apr_pool_t *p, server_rec *server)
{
include_server_config *result;
result = apr_palloc(p, sizeof(include_server_config));
result->default_end_tag = DEFAULT_END_SEQUENCE;
result->default_start_tag = DEFAULT_START_SEQUENCE;
return result;
}
static const char *set_xbithack(cmd_parms *cmd, void *mconfig, const char *arg)
{
include_dir_config *conf = mconfig;
if (!strcasecmp(arg, "off")) {
conf->xbithack = XBITHACK_OFF;
}
else if (!strcasecmp(arg, "on")) {
conf->xbithack = XBITHACK_ON;
}
else if (!strcasecmp(arg, "full")) {
conf->xbithack = XBITHACK_FULL;
}
else {
return "XBitHack must be set to Off, On, or Full";
}
return NULL;
}
static const char *set_default_start_tag(cmd_parms *cmd, void *mconfig,
const char *tag)
{
include_server_config *conf;
const char *p = tag;
/* be consistent. (See below in set_default_end_tag) */
while (*p) {
if (apr_isspace(*p)) {
return "SSIStartTag may not contain any whitespaces";
}
++p;
}
conf= ap_get_module_config(cmd->server->module_config , &include_module);
conf->default_start_tag = tag;
return NULL;
}
static const char *set_default_end_tag(cmd_parms *cmd, void *mconfig,
const char *tag)
{
include_server_config *conf;
const char *p = tag;
/* sanity check. The parser may fail otherwise */
while (*p) {
if (apr_isspace(*p)) {
return "SSIEndTag may not contain any whitespaces";
}
++p;
}
conf= ap_get_module_config(cmd->server->module_config , &include_module);
conf->default_end_tag = tag;
return NULL;
}
static const char *set_undefined_echo(cmd_parms *cmd, void *mconfig,
const char *msg)
{
include_dir_config *conf = mconfig;
conf->undefined_echo = msg;
return NULL;
}
static const char *set_default_error_msg(cmd_parms *cmd, void *mconfig,
const char *msg)
{
include_dir_config *conf = mconfig;
conf->default_error_msg = msg;
return NULL;
}
static const char *set_default_time_fmt(cmd_parms *cmd, void *mconfig,
const char *fmt)
{
include_dir_config *conf = mconfig;
conf->default_time_fmt = fmt;
return NULL;
}
/*
* +-------------------------------------------------------+
* | |
* | Module Initialization and Configuration
* | |
* +-------------------------------------------------------+
*/
static int include_post_config(apr_pool_t *p, apr_pool_t *plog,
apr_pool_t *ptemp, server_rec *s)
{
include_handlers = apr_hash_make(p);
ssi_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_include_handler);
if (ssi_pfn_register) {
ssi_pfn_register("if", handle_if);
ssi_pfn_register("set", handle_set);
ssi_pfn_register("else", handle_else);
ssi_pfn_register("elif", handle_elif);
ssi_pfn_register("echo", handle_echo);
ssi_pfn_register("endif", handle_endif);
ssi_pfn_register("fsize", handle_fsize);
ssi_pfn_register("config", handle_config);
ssi_pfn_register("comment", handle_comment);
ssi_pfn_register("include", handle_include);
ssi_pfn_register("flastmod", handle_flastmod);
ssi_pfn_register("printenv", handle_printenv);
}
return OK;
}
static const command_rec includes_cmds[] =
{
AP_INIT_TAKE1("XBitHack", set_xbithack, NULL, OR_OPTIONS,
"Off, On, or Full"),
AP_INIT_TAKE1("SSIErrorMsg", set_default_error_msg, NULL, OR_ALL,
"a string"),
AP_INIT_TAKE1("SSITimeFormat", set_default_time_fmt, NULL, OR_ALL,
"a strftime(3) formatted string"),
AP_INIT_TAKE1("SSIStartTag", set_default_start_tag, NULL, RSRC_CONF,
"SSI Start String Tag"),
AP_INIT_TAKE1("SSIEndTag", set_default_end_tag, NULL, RSRC_CONF,
"SSI End String Tag"),
AP_INIT_TAKE1("SSIUndefinedEcho", set_undefined_echo, NULL, OR_ALL,
"String to be displayed if an echoed variable is undefined"),
AP_INIT_FLAG("SSILegacyExprParser", ap_set_flag_slot_char,
(void *)APR_OFFSETOF(include_dir_config, legacy_expr),
OR_LIMIT,
"Whether to use the legacy expression parser compatible "
"with <= 2.2.x. Limited to 'on' or 'off'"),
AP_INIT_FLAG("SSILastModified", ap_set_flag_slot_char,
(void *)APR_OFFSETOF(include_dir_config, lastmodified),
OR_LIMIT, "Whether to set the last modified header or respect "
"an existing header. Limited to 'on' or 'off'"),
AP_INIT_FLAG("SSIEtag", ap_set_flag_slot_char,
(void *)APR_OFFSETOF(include_dir_config, etag),
OR_LIMIT, "Whether to allow the generation of ETags within the server. "
"Existing ETags will be preserved. Limited to 'on' or 'off'"),
{NULL}
};
static void ap_register_include_handler(char *tag, include_handler_fn_t *func)
{
apr_hash_set(include_handlers, tag, strlen(tag), (const void *)func);
}
static void register_hooks(apr_pool_t *p)
{
APR_REGISTER_OPTIONAL_FN(ap_ssi_get_tag_and_value);
APR_REGISTER_OPTIONAL_FN(ap_ssi_parse_string);
APR_REGISTER_OPTIONAL_FN(ap_register_include_handler);
ap_hook_post_config(include_post_config, NULL, NULL, APR_HOOK_REALLY_FIRST);
ap_hook_fixups(include_fixup, NULL, NULL, APR_HOOK_LAST);
ap_register_output_filter("INCLUDES", includes_filter, includes_setup,
AP_FTYPE_RESOURCE);
}
AP_DECLARE_MODULE(include) =
{
STANDARD20_MODULE_STUFF,
create_includes_dir_config, /* dir config creater */
merge_includes_dir_config, /* dir config merger */
create_includes_server_config,/* server config */
NULL, /* merge server config */
includes_cmds, /* command apr_table_t */
register_hooks /* register hooks */
};