X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=modules%2Ffilters%2Fmod_include.c;h=cf1535c7a10c4fc00b94c04765af157067eecbbf;hb=aa21671e13767135f0ee3f88d6a3ff6d039e6534;hp=3c208efcb362181a720134d34311ee3846bce09a;hpb=2c6d3eef0bb76a41575ee3b7ef21298731141aca;p=apache diff --git a/modules/filters/mod_include.c b/modules/filters/mod_include.c index 3c208efcb3..cf1535c7a1 100644 --- a/modules/filters/mod_include.c +++ b/modules/filters/mod_include.c @@ -1,7 +1,7 @@ /* ==================================================================== * The Apache Software License, Version 1.1 * - * Copyright (c) 2000-2002 The Apache Software Foundation. All rights + * Copyright (c) 2000-2003 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -73,10 +73,9 @@ #include "apr_optional.h" #define APR_WANT_STRFUNC +#define APR_WANT_MEMFUNC #include "apr_want.h" -#define CORE_PRIVATE - #include "ap_config.h" #include "util_filter.h" #include "httpd.h" @@ -90,51 +89,412 @@ #include "util_script.h" #include "http_core.h" #include "mod_include.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 */ -module AP_MODULE_DECLARE_DATA include_module; -static apr_hash_t *include_hash; -static APR_OPTIONAL_FN_TYPE(ap_register_include_handler) *ssi_pfn_register; -/***************************************************************** - * - * XBITHACK. Sigh... NB it's configurable per-directory; the compile-time - * option only changes the default. +/* + * +-------------------------------------------------------+ + * | | + * | Types and Structures + * | | + * +-------------------------------------------------------+ */ -enum xbithack { - xbithack_off, xbithack_on, xbithack_full -}; +/* 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_type_t; -struct bndm_t { - unsigned int T[256]; - unsigned int x; -} ; +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_t; typedef struct { - char *default_error_msg; - char *default_time_fmt; - enum xbithack *xbithack; + const char *default_error_msg; + const char *default_time_fmt; + const char *undefined_echo; + xbithack_t xbithack; } include_dir_config; typedef struct { - char *default_start_tag; - char *default_end_tag; - int start_tag_len; - bndm_t start_seq_pat; - char *undefinedEcho; - int undefinedEchoLen; + const char *default_start_tag; + const char *default_end_tag; } include_server_config; -#ifdef XBITHACK -#define DEFAULT_XBITHACK xbithack_full -#else -#define DEFAULT_XBITHACK xbithack_off +/* 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; + +#define MAX_NMATCH 10 + +typedef struct { + const char *source; + const char *rexp; + apr_size_t nsub; + regmatch_t match[MAX_NMATCH]; +} 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; + + request_rec *r; + 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; + +#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"); + + return; +} + +#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 + * | | + * +-------------------------------------------------------+ + */ -#define BYTE_COUNT_THRESHOLD AP_MIN_BYTES_TO_WRITE +/* global module structure */ +module AP_MODULE_DECLARE_DATA include_module; + +/* function handlers for include directives */ +static apr_hash_t *include_handlers; -/* ------------------------ Environment function -------------------------- */ +/* 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 @@ -142,7 +502,133 @@ typedef struct { static const char lazy_eval_sentinel; #define LAZY_VALUE (&lazy_eval_sentinel) -static void add_include_vars(request_rec *r, char *timefmt) +/* default values */ +#define DEFAULT_START_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)" + +#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, � 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. */ + +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, const char *timefmt) { apr_table_t *e = r->subprocess_env; char *t; @@ -192,7 +678,7 @@ static const char *add_include_vars_lazy(request_rec *r, const char *var) val = ap_ht_time(r->pool, r->finfo.mtime, conf->default_time_fmt, 0); } else if (!strcasecmp(var, "USER_NAME")) { - if (apr_get_username(&val, r->finfo.user, r->pool) != APR_SUCCESS) { + if (apr_uid_name_get(&val, r->finfo.user, r->pool) != APR_SUCCESS) { val = ""; } } @@ -206,1318 +692,888 @@ static const char *add_include_vars_lazy(request_rec *r, const char *var) return val; } -static const char *get_include_var(request_rec *r, include_ctx_t *ctx, - const char *var) +static const char *get_include_var(const char *var, include_ctx_t *ctx) { const char *val; + request_rec *r = ctx->intern->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 (!ctx->re_result || !ctx->re_string) { + if (!re) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "regex capture $%d " + "refers to no regex in %s", idx, r->filename); return NULL; } else { - int idx = atoi(var); - apr_size_t len = (*ctx->re_result)[idx].rm_eo - - (*ctx->re_result)[idx].rm_so; - if ( (*ctx->re_result)[idx].rm_so < 0 - || (*ctx->re_result)[idx].rm_eo < 0) { + if (re->nsub < idx) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "regex capture $%d is out of range (last regex " + "was: '%s') in %s", idx, re->rexp, r->filename); + return NULL; + } + + if (re->match[idx].rm_so < 0 || re->match[idx].rm_eo < 0) { return NULL; } - val = apr_pstrmemdup(r->pool, ctx->re_string - + (*ctx->re_result)[idx].rm_so, len); + + 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) + if (val == LAZY_VALUE) { val = add_include_vars_lazy(r, var); + } } + return val; } -/* --------------------------- Parser functions --------------------------- */ - -/* 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 +/* + * Do variable substitution on strings * - * Initial code submitted by Sascha Schumann. + * (Note: If out==NULL, this function allocs a buffer for the resulting + * string from ctx->pool. The return value is always the parsed string) */ - -/* Precompile the bndm_t data structure. */ -static void bndm_compile(bndm_t *t, const char *n, apr_size_t nl) +static char *ap_ssi_parse_string(include_ctx_t *ctx, const char *in, char *out, + apr_size_t length, int leave_name) { - unsigned int x; - const char *ne = n + nl; + request_rec *r = ctx->intern->r; + result_item_t *result = NULL, *current = NULL; + apr_size_t outlen = 0, inlen, span; + char *ret = NULL, *eout = NULL; + const char *p; - memset(t->T, 0, sizeof(unsigned int) * 256); - - for (x = 1; n < ne; x <<= 1) - t->T[(unsigned char) *n++] |= x; + if (out) { + /* sanity check, out && !length is not supported */ + ap_assert(out && length); - t->x = x - 1; -} + ret = out; + eout = out + length - 1; + } -/* Implements the BNDM search algorithm (as described above). - * - * n - the pattern to search for - * nl - length of the pattern to search for - * 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(const char *n, apr_size_t nl, const char *h, - apr_size_t hl, bndm_t *t) -{ - const char *skip; - const char *he, *p, *pi; - unsigned int *T, x, d; + span = strcspn(in, "\\$"); + inlen = strlen(in); - he = h + hl; + /* 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); + } - T = t->T; - x = t->x; + return ret; + } - pi = h - 1; /* pi: p initial */ - p = pi + nl; /* compare window right to left. point to the first char */ + /* well, actually something to do */ + p = in + span; - 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; -} - -/* We've now found a start sequence tag... */ -static apr_bucket* found_start_sequence(apr_bucket *dptr, - include_ctx_t *ctx, - apr_size_t tagStart, - apr_size_t len) -{ - /* We want to split the bucket at the '<'. */ - ctx->state = PARSE_DIRECTIVE; - ctx->tag_length = 0; - ctx->parse_pos = 0; - - /* If tagStart indexes the end of the bucket, then tag_start_bucket - * should be the next bucket - */ - if (tagStart < len) { - ctx->tag_start_bucket = dptr; - ctx->tag_start_index = tagStart; + if (out) { + if (span) { + memcpy(out, in, (out+span <= eout) ? span : (eout-out)); + out += span; + } } else { - ctx->tag_start_bucket = APR_BUCKET_NEXT(dptr); - ctx->tag_start_index = 0; + current = result = apr_palloc(ctx->dpool, sizeof(*result)); + current->next = NULL; + current->string = in; + current->len = span; + outlen = span; } - if (ctx->head_start_index > 0) { - apr_bucket *tmp_bkt; + /* loop for specials */ + do { + if ((out && out >= eout) || (length && outlen >= length)) { + break; + } - /* Split the bucket with the start of the tag in it */ - apr_bucket_split(ctx->head_start_bucket, ctx->head_start_index); - tmp_bkt = APR_BUCKET_NEXT(ctx->head_start_bucket); - /* If it was a one bucket match */ - if ((tagStart < len) && (dptr == ctx->head_start_bucket)) { - ctx->tag_start_bucket = tmp_bkt; - ctx->tag_start_index = tagStart - ctx->head_start_index; + /* 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; } - ctx->head_start_bucket = tmp_bkt; - ctx->head_start_index = 0; - } - return ctx->head_start_bucket; -} -/* This function returns either a pointer to the split bucket containing the - * first byte of the BEGINNING_SEQUENCE (after finding a complete match) or it - * returns NULL if no match found. - */ -static apr_bucket *find_start_sequence(apr_bucket *dptr, include_ctx_t *ctx, - apr_bucket_brigade *bb, int *do_cleanup) -{ - apr_size_t len; - const char *c; - const char *buf; - const char *str = ctx->start_seq ; - apr_size_t slen = ctx->start_seq_len; - apr_size_t pos; + /* + * escaped character + */ + if (*p == '\\') { + if (out) { + *out++ = (p[1] == '$') ? *++p : *p; + ++p; + } + else { + current->len = 1; + current->string = (p[1] == '$') ? ++p : p; + ++p; + ++outlen; + } + } - *do_cleanup = 0; + /* + * 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, "Missing '}' on " + "variable \"%s\" in %s", p, r->filename); + break; + } - do { - apr_status_t rv = 0; - int read_done = 0; + 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 (APR_BUCKET_IS_EOS(dptr)) { - break; - } + if (p < ep) { + key = apr_pstrmemdup(ctx->dpool, p, ep - p); + newp = ep; + } + --p; + } -#if 0 - /* XXX the bucket flush support is commented out for now - * because it was causing a segfault */ - if (APR_BUCKET_IS_FLUSH(dptr)) { - apr_bucket *old = dptr; - dptr = APR_BUCKET_NEXT(old); - APR_BUCKET_REMOVE(old); - ctx->output_now = 1; - ctx->output_flush = 1; - } - else -#endif /* 0 */ - if (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD) { - ctx->output_now = 1; - } - else if (ctx->bytes_parsed > 0) { - rv = apr_bucket_read(dptr, &buf, &len, APR_NONBLOCK_READ); - read_done = 1; - if (APR_STATUS_IS_EAGAIN(rv)) { - ctx->output_now = 1; - } - } - - if (ctx->output_now) { - apr_bucket *start_bucket; - if (ctx->head_start_index > 0) { - start_bucket = ctx->head_start_bucket; - apr_bucket_split(start_bucket, ctx->head_start_index); - start_bucket = APR_BUCKET_NEXT(start_bucket); - ctx->head_start_index = 0; - ctx->head_start_bucket = start_bucket; - ctx->parse_pos = 0; - ctx->state = PRE_HEAD; + /* 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 { - start_bucket = dptr; - } - return start_bucket; - } + const char *val = get_include_var(key, ctx); + apr_size_t len = 0; - if (!read_done) { - rv = apr_bucket_read(dptr, &buf, &len, APR_BLOCK_READ); - } - if (!APR_STATUS_IS_SUCCESS(rv)) { - ctx->status = rv; - return NULL; + 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 (len == 0) { /* end of pipe? */ + if ((out && out >= eout) || (length && outlen >= length)) { break; } - /* Set our buffer to use. */ - c = buf; - - /* The last bucket had a left over partial match that we need to - * complete. - */ - if (ctx->state == PARSE_HEAD) - { - apr_size_t tmpLen; - tmpLen = (len < (slen - 1)) ? len : (slen - 1); - - while (c < buf + tmpLen && *c == str[ctx->parse_pos]) - { - c++; - ctx->parse_pos++; + /* 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 (str[ctx->parse_pos] == '\0') - { - ctx->bytes_parsed += c - buf; - return found_start_sequence(dptr, ctx, c - buf, len); + if (out) { + memcpy(out, p, (out+span <= eout) ? span : (eout-out)); + out += span; } - else if (c == buf + tmpLen) { - dptr = APR_BUCKET_NEXT(dptr); - continue; + else { + current->len = span; + current->string = p; + outlen += span; } - /* False alarm... - */ - APR_BRIGADE_PREPEND(bb, ctx->ssi_tag_brigade); - - /* We know we are at the beginning of this bucket so - * we can just prepend the saved bytes from the - * ssi_tag_brigade (which empties the ssi_tag_brigade) - * and continue processing. - * We do not need to set do_cleanup beacuse the - * prepend takes care of that. - */ - ctx->state = PRE_HEAD; - ctx->head_start_bucket = NULL; - ctx->head_start_index = 0; + p += span; } + } while (p < in+inlen); - if (len) - { - pos = bndm(str, slen, c, len, ctx->start_seq_pat); - if (pos != len) - { - ctx->head_start_bucket = dptr; - ctx->head_start_index = pos; - ctx->bytes_parsed += pos + slen; - return found_start_sequence(dptr, ctx, pos + slen, len); - } - } - - /* Consider the case where we have . This makes the - * second check after the original check fails. - * If parse_pos was already 0 then we already checked - * this. - */ - ctx->tag_length += ctx->parse_pos; - - if (*c == str[0]) { - ctx->state = PARSE_TAIL; - ctx->tail_start_bucket = dptr; - ctx->tail_start_index = c - buf; - ctx->parse_pos = 1; - } - else { - ctx->tag_length++; - if (ctx->tag_length > ctx->directive_length) { - ctx->state = PARSE_TAG; - } - else { - ctx->state = PARSE_DIRECTIVE; - ctx->directive_length += ctx->parse_pos; - } - ctx->tail_start_bucket = NULL; - ctx->tail_start_index = 0; - ctx->parse_pos = 0; - } - } + + if (found) { + break; } } - c++; } - ctx->bytes_parsed += (c - start); - dptr = APR_BUCKET_NEXT(dptr); - } while (dptr != APR_BRIGADE_SENTINEL(bb)); - return NULL; -} - -/* This function culls through the buckets that have been set aside in the - * ssi_tag_brigade and copies just the directive part of the SSI tag (none - * of the start and end delimiter bytes are copied). - */ -static apr_status_t get_combined_directive (include_ctx_t *ctx, - request_rec *r, - apr_bucket_brigade *bb, - char *tmp_buf, - apr_size_t tmp_buf_size) -{ - int done = 0; - apr_bucket *dptr; - const char *tmp_from; - apr_size_t tmp_from_len; - - /* If the tag length is longer than the tmp buffer, allocate space. */ - if (ctx->tag_length > tmp_buf_size-1) { - if ((ctx->combined_tag = apr_pcalloc(r->pool, - ctx->tag_length + 1)) == NULL) { - return (APR_ENOMEM); - } - } /* Else, just use the temp buffer. */ - else { - ctx->combined_tag = tmp_buf; } - /* Prime the pump. Start at the beginning of the tag... */ - dptr = ctx->tag_start_bucket; - /* Read the bucket... */ - apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0); - - /* Adjust the pointer to start at the tag within the bucket... */ - if (dptr == ctx->tail_start_bucket) { - tmp_from_len -= (tmp_from_len - ctx->tail_start_index); + if (unmatched) { + token->value = apr_pstrdup(pool, ""); } - tmp_from = &tmp_from[ctx->tag_start_index]; - tmp_from_len -= ctx->tag_start_index; - ctx->curr_tag_pos = ctx->combined_tag; + else { + apr_size_t len = p - token->value - shift; + char *c = apr_palloc(pool, len + 1); - /* Loop through the buckets from the tag_start_bucket until before - * the tail_start_bucket copying the contents into the buffer. - */ - do { - memcpy (ctx->curr_tag_pos, tmp_from, tmp_from_len); - ctx->curr_tag_pos += tmp_from_len; + p = token->value; + token->value = c; - if (dptr == ctx->tail_start_bucket) { - done = 1; - } - else { - dptr = APR_BUCKET_NEXT (dptr); - apr_bucket_read (dptr, &tmp_from, &tmp_from_len, 0); - /* Adjust the count to stop at the beginning of the tail. */ - if (dptr == ctx->tail_start_bucket) { - tmp_from_len -= (tmp_from_len - ctx->tail_start_index); - } + 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; } - } while ((!done) && - (ctx->curr_tag_pos < ctx->combined_tag + ctx->tag_length)); - ctx->combined_tag[ctx->tag_length] = '\0'; - ctx->curr_tag_pos = ctx->combined_tag; + if (len) { + memcpy(c, p, len); + } + c[len] = '\0'; + } - return (APR_SUCCESS); + return unmatched; } -/* - * 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, � will not be decoded, but will be deleted. - * - * drtr - */ +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->intern->r; + const char *error = "Invalid expression \"%s\" in file %s"; + const char *parse = expr; + int was_unmatched = 0; + unsigned regex = 0; -/* maximum length of any ISO-LATIN-1 HTML entity name. */ -#define MAXENTLEN (6) + *was_error = 0; -/* The following is a shrinking transformation, therefore safe. */ + if (!parse) { + return 0; + } -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\353\ -iuml\357ouml\366uuml\374yuml\377", /* 4 */ - "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\ -THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\ -ucirc\373thorn\376", /* 5 */ - "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\ -Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\ -Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\ -egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\ -otilde\365oslash\370ugrave\371uacute\372yacute\375" /* 6 */ - }; + /* Create Parse Tree */ + while (1) { + /* uncomment this to see how the tree a built: + * + * DEBUG_DUMP_TREE(ctx, root); + */ + CREATE_NODE(ctx, new); - /* Do a fast scan through the string until we find anything - * that needs more complicated handling - */ - for (; *s != '&'; s++) { - if (*s == '\0') { - return; + was_unmatched = get_ptoken(ctx->dpool, &parse, &new->token); + if (!parse) { + break; } - } - 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; - } + DEBUG_DUMP_UNMATCHED(ctx, was_unmatched); + DEBUG_DUMP_TOKEN(ctx, &new->token); - if (s[i] == '\0') { /* treat as normal data */ - *p = *s; - continue; - } + if (!current) { + switch (new->token.type) { + case TOKEN_STRING: + case TOKEN_NOT: + case TOKEN_LBRACE: + root = current = new; + 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); + default: + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, + r->filename); + *was_error = 1; + return 0; } } - 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'; -} - -/* - * 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.. - * format: - * [WS][WS]=[WS]['|"|`][['|"|`|]|WS] - */ -#define SKIP_TAG_WHITESPACE(ptr) while ((*ptr != '\0') && (apr_isspace (*ptr))) ptr++ - -static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag, - char **tag_val, int dodecode) -{ - char *c = ctx->curr_tag_pos; - int shift_val = 0; - char term = '\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; - *tag_val = NULL; - if (ctx->curr_tag_pos > ctx->combined_tag + ctx->tag_length) { - *tag = NULL; - return; - } - SKIP_TAG_WHITESPACE(c); - *tag = c; /* First non-whitespace character (could be NULL). */ + case TOKEN_RE: + case TOKEN_RBRACE: + case TOKEN_GROUP: + break; - while (apr_islower(*c)) { - c++; /* Optimization for the common case where the tag */ - } /* is already lowercase */ + default: + new->parent = current; + current = current->right = new; + continue; + } + break; - while ((*c != '=') && (!apr_isspace(*c)) && (*c != '\0')) { - *c = apr_tolower(*c); /* find end of tag, lowercasing as we go... */ - c++; - } + case TOKEN_RE: + switch (current->token.type) { + case TOKEN_EQ: + case TOKEN_NE: + new->parent = current; + current = current->right = new; + ++regex; + continue; - if ((*c == '\0') || (**tag == '=')) { - if ((**tag == '\0') || (**tag == '=')) { - *tag = NULL; - } - ctx->curr_tag_pos = c; - return; /* We have found the end of the buffer. */ - } /* We might have a tag, but definitely no value. */ + default: + break; + } + break; - if (*c == '=') { - *c++ = '\0'; /* Overwrite the '=' with a terminating byte after tag. */ - } - else { /* Try skipping WS to find the '='. */ - *c++ = '\0'; /* Terminate the tag... */ - SKIP_TAG_WHITESPACE(c); - - /* There needs to be an equal sign if there's a value. */ - if (*c != '=') { - ctx->curr_tag_pos = c; - return; /* There apparently was no value. */ - } - else { - c++; /* Skip the equals sign. */ - } - } + case TOKEN_AND: + case TOKEN_OR: + switch (current->token.type) { + case TOKEN_STRING: + case TOKEN_RE: + case TOKEN_GROUP: + current = current->parent; - SKIP_TAG_WHITESPACE(c); - if (*c == '"' || *c == '\'' || *c == '`') { - /* Allow quoted values for space inclusion. - * NOTE: This does not pass the quotes on return. - */ - term = *c++; - } - - *tag_val = c; - if (!term) { - while (!apr_isspace(*c) && (*c != '\0')) { - c++; - } - } - else { - while ((*c != term) && (*c != '\0') && (*c != '\\')) { - /* Quickly scan past the string until we reach - * either the end of the tag or a backslash. If - * we find a backslash, we have to switch to the - * more complicated parser loop that follows. - */ - c++; - } - if (*c == '\\') { - do { - /* Accept \" (or ' or `) as valid quotation of string. - */ - if (*c == '\\') { - /* Overwrite the "\" during the embedded - * escape sequence of '"'. "\'" or '`'. - * Shift bytes from here to next delimiter. - */ - c++; - if (*c == term) { - shift_val++; - } - if (shift_val > 0) { - *(c-shift_val) = *c; - } - if (*c == '\0') { + while (current) { + switch (current->token.type) { + case TOKEN_AND: + case TOKEN_OR: + case TOKEN_LBRACE: break; + + default: + current = current->parent; + continue; } + break; } - c++; - if (shift_val > 0) { - *(c-shift_val) = *c; + if (!current) { + new->left = root; + root->parent = new; + current = root = new; + continue; } - } while ((*c != term) && (*c != '\0')); - } - } - - *(c-shift_val) = '\0'; /* Overwrites delimiter (term or WS) with NULL. */ - ctx->curr_tag_pos = ++c; - if (dodecode) { - decodehtml(*tag_val); - } - - return; -} + + new->left = current->right; + new->left->parent = new; + new->parent = current; + current = current->right = new; + continue; -/* initial buffer size for power-of-two allocator in ap_ssi_parse_string */ -#define PARSE_STRING_INITIAL_SIZE 64 + default: + break; + } + break; -/* - * Do variable substitution on strings - * (Note: If out==NULL, this function allocs a buffer for the resulting - * string from r->pool. The return value is the parsed string) - */ -static char *ap_ssi_parse_string(request_rec *r, include_ctx_t *ctx, - const char *in, char *out, - apr_size_t length, int leave_name) -{ - char ch; - char *next; - char *end_out; - apr_size_t out_size; + 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; - /* allocate an output buffer if needed */ - if (!out) { - out_size = PARSE_STRING_INITIAL_SIZE; - if (out_size > length) { - out_size = length; - } - out = apr_palloc(r->pool, out_size); - } - else { - out_size = length; - } - - /* leave room for nul terminator */ - end_out = out + out_size - 1; - - next = out; - while ((ch = *in++) != '\0') { - switch (ch) { - case '\\': - if (next == end_out) { - if (out_size < length) { - /* double the buffer size */ - apr_size_t new_out_size = out_size * 2; - apr_size_t current_length = next - out; - char *new_out; - if (new_out_size > length) { - new_out_size = length; - } - new_out = apr_palloc(r->pool, new_out_size); - memcpy(new_out, out, current_length); - out = new_out; - out_size = new_out_size; - end_out = out + out_size - 1; - next = out + current_length; + if (!current) { + new->left = root; + root->parent = new; + current = root = new; + continue; } - else { - /* truncated */ - *next = '\0'; - return out; + + 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; } } - if (*in == '$') { - *next++ = *in++; + break; + + case TOKEN_RBRACE: + while (current && current->token.type != TOKEN_LBRACE) { + current = current->parent; } - else { - *next++ = ch; + + if (current) { + TYPE_TOKEN(¤t->token, TOKEN_GROUP); + continue; } + + error = "Unmatched ')' in \"%s\" in file %s"; break; - case '$': - { - const char *start_of_var_name; - char *end_of_var_name; /* end of var name + 1 */ - const char *expansion, *temp_end, *val; - char tmp_store; - size_t l; - - /* guess that the expansion won't happen */ - expansion = in - 1; - if (*in == '{') { - ++in; - start_of_var_name = in; - in = ap_strchr_c(in, '}'); - if (in == NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, - 0, r, "Missing '}' on variable \"%s\"", - expansion); - *next = '\0'; - return out; - } - temp_end = in; - end_of_var_name = (char *)temp_end; - ++in; - } - else { - start_of_var_name = in; - while (apr_isalnum(*in) || *in == '_') { - ++in; - } - temp_end = in; - end_of_var_name = (char *)temp_end; - } - /* what a pain, too bad there's no table_getn where you can - * pass a non-nul terminated string */ - l = end_of_var_name - start_of_var_name; - if (l != 0) { - tmp_store = *end_of_var_name; - *end_of_var_name = '\0'; - val = get_include_var(r, ctx, start_of_var_name); - *end_of_var_name = tmp_store; - - if (val) { - expansion = val; - l = strlen(expansion); - } - else if (leave_name) { - l = in - expansion; - } - else { - /* no expansion to be done */ - break; - } - } - else { - /* zero-length variable name causes just the $ to be - * copied */ - l = 1; - } - if ((next + l > end_out) && (out_size < length)) { - /* increase the buffer size to accommodate l more chars */ - apr_size_t new_out_size = out_size; - apr_size_t current_length = next - out; - char *new_out; - do { - new_out_size *= 2; - } while (new_out_size < current_length + l); - if (new_out_size > length) { - new_out_size = length; - } - new_out = apr_palloc(r->pool, new_out_size); - memcpy(new_out, out, current_length); - out = new_out; - out_size = new_out_size; - end_out = out + out_size - 1; - next = out + current_length; - } - l = ((int)l > end_out - next) ? (end_out - next) : l; - memcpy(next, expansion, l); - next += l; + + case TOKEN_NOT: + 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: - if (next == end_out) { - if (out_size < length) { - /* double the buffer size */ - apr_size_t new_out_size = out_size * 2; - apr_size_t current_length = next - out; - char *new_out; - if (new_out_size > length) { - new_out_size = length; - } - new_out = apr_palloc(r->pool, new_out_size); - memcpy(new_out, out, current_length); - out = new_out; - out_size = new_out_size; - end_out = out + out_size - 1; - next = out + current_length; - } - else { - /* truncated */ - *next = '\0'; - return out; - } - } - *next++ = ch; break; } - } - *next = '\0'; - return out; -} - -/* --------------------------- Action handlers ---------------------------- */ -/* ensure that path is relative, and does not contain ".." elements - * ensentially ensure that it does not match the regex: - * (^/|(^|/)\.\.(/|$)) - * XXX: Simply replace with apr_filepath_merge - */ -static int is_only_below(const char *path) -{ -#ifdef HAVE_DRIVE_LETTERS - if (path[1] == ':') - return 0; -#endif -#ifdef NETWARE - if (ap_strchr_c(path, ':')) - return 0; -#endif - if (path[0] == '/') { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, expr, r->filename); + *was_error = 1; return 0; } - while (*path) { - int dots = 0; - while (path[dots] == '.') - ++dots; -#if defined(WIN32) - /* If the name is canonical this is redundant - * but in security, redundancy is worthwhile. - * Does OS2 belong here (accepts ... for ..)? - */ - if (dots > 1 && (!path[dots] || path[dots] == '/')) - return 0; -#else - if (dots == 2 && (!path[dots] || path[dots] == '/')) - return 0; -#endif - path += dots; - /* Advance to either the null byte at the end of the - * string or the character right after the next slash, - * whichever comes first - */ - while (*path && (*path++ != '/')) { - continue; - } - } - return 1; -} -static int handle_include(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) -{ - char *tag = NULL; - char *tag_val = NULL; - apr_bucket *tmp_buck; - char *parsed_string; - int loglevel = APLOG_ERR; - - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if (tag_val == NULL) { - if (tag == NULL) { - return (0); - } - else { - return (1); - } + 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, + "Invalid expression \"%s\" in file %s", + expr, r->filename); + *was_error = 1; + return 0; } - if (!strcmp(tag, "virtual") || !strcmp(tag, "file")) { - request_rec *rr = NULL; - char *error_fmt = NULL; - apr_status_t rc = APR_SUCCESS; - SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, rc); - if (rc != APR_SUCCESS) { - return rc; - } - - parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0); - if (tag[0] == 'f') { - /* XXX: Port to apr_filepath_merge - * be safe; only files in this directory or below allowed - */ - if (!is_only_below(parsed_string)) { - error_fmt = "unable to include file \"%s\" " - "in parsed file %s"; - } - else { - rr = ap_sub_req_lookup_uri(parsed_string, r, f->next); - } - } - else { - rr = ap_sub_req_lookup_uri(parsed_string, r, f->next); - } + 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; - if (!error_fmt && rr->status != HTTP_OK) { - error_fmt = "unable to include \"%s\" in parsed file %s"; + default: + current = current->left; + continue; } + } - if (!error_fmt && (ctx->flags & FLAG_NO_EXEC) && - rr->content_type && - (strncmp(rr->content_type, "text/", 5))) { - error_fmt = "unable to include potential exec \"%s\" " - "in parsed file %s"; - } - if (error_fmt == NULL) { - /* try to avoid recursive includes. We do this by walking - * up the r->main list of subrequests, and at each level - * walking back through any internal redirects. At each - * step, we compare the filenames and the URIs. - * - * The filename comparison catches a recursive include - * with an ever-changing URL, eg. - * - * which, although they would eventually be caught because - * we have a limit on the length of files, etc., can - * recurse for a while. - * - * The URI comparison catches the case where the filename - * is changed while processing the request, so the - * current name is never the same as any previous one. - * This can happen with "DocumentRoot /foo" when you - * request "/" on the server and it includes "/". - * This only applies to modules such as mod_dir that - * (somewhat improperly) mess with r->filename outside - * of a filename translation phase. - */ - int founddupe = 0; - request_rec *p; - for (p = r; p != NULL && !founddupe; p = p->main) { - request_rec *q; - for (q = p; q != NULL; q = q->prev) { - if ((q->filename && rr->filename && - (strcmp(q->filename, rr->filename) == 0)) || - ((*q->uri == '/') && - (strcmp(q->uri, rr->uri) == 0))) - { - founddupe = 1; - break; - } - } - } + /* 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; - if (p != NULL) { - error_fmt = "Recursive include of \"%s\" " - "in parsed file %s"; + default: + current = current->right; + continue; } } - /* See the Kludge in send_parsed_file 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 && ap_run_sub_req(rr)) { - error_fmt = "unable to include \"%s\" in parsed file %s"; + if (current->token.type == TOKEN_AND) { + current->value = current->left->value && + current->right->value; } - if (error_fmt) { - ap_log_rerror(APLOG_MARK, loglevel, - 0, r, error_fmt, tag_val, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); + else { + current->value = current->left->value || + current->right->value; } + } + break; - /* destroy the sub request */ - if (rr != NULL) { - ap_destroy_sub_req(rr); - } + 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, + "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, - "unknown parameter \"%s\" to tag include in %s", - tag, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); + "Invalid expression \"%s\" in file %s", + expr, r->filename); + *was_error = 1; + return 0; } - } - } - 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); -static int handle_echo(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) -{ - char *tag = NULL; - char *tag_val = NULL; - const char *echo_text = NULL; - apr_bucket *tmp_buck; - apr_size_t e_len; - enum {E_NONE, E_URL, E_ENTITY} encode; + current->value = strcmp(current->left->token.value, + current->right->token.value); - encode = E_ENTITY; + 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; - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if (tag_val == NULL) { - if (tag != NULL) { - return 1; - } - else { - return 0; + 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 (!strcmp(tag, "var")) { - conn_rec *c = r->connection; - const char *val = - get_include_var(r, ctx, - ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0)); - if (val) { - switch(encode) { - case E_NONE: - echo_text = val; - break; - case E_URL: - echo_text = ap_escape_uri(r->pool, val); - break; - case E_ENTITY: - echo_text = ap_escape_html(r->pool, val); - break; - } - e_len = strlen(echo_text); - tmp_buck = apr_bucket_pool_create(echo_text, e_len, - r->pool, c->bucket_alloc); - } - else { - include_server_config *sconf= - ap_get_module_config(r->server->module_config, - &include_module); - tmp_buck = apr_bucket_pool_create(sconf->undefinedEcho, - sconf->undefinedEchoLen, - r->pool, c->bucket_alloc); - } - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; - } + if (current->token.type == TOKEN_NOT) { + current->value = !current->value; } - else if (!strcmp(tag, "encoding")) { - if (!strcasecmp(tag_val, "none")) encode = E_NONE; - else if (!strcasecmp(tag_val, "url")) encode = E_URL; - else if (!strcasecmp(tag_val, "entity")) encode = E_ENTITY; - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown value \"%s\" to parameter \"encoding\" of " - "tag echo in %s", tag_val, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - } + break; + + case TOKEN_RE: + if (!error) { + error = "No operator before regex in expr \"%s\" in file %s"; } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown parameter \"%s\" in tag echo of %s", - tag, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); + case TOKEN_LBRACE: + if (!error) { + error = "Unmatched '(' in \"%s\" in file %s"; + } + default: + if (!error) { + error = "internal parser error in \"%s\" in file %s"; } + 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 0; + + return (root ? root->value : 0); } -/* error and tf must point to a string with room for at - * least MAX_STRING_LEN characters + +/* + * +-------------------------------------------------------+ + * | | + * | 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 int handle_config(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) +static void ap_ssi_get_tag_and_value(include_ctx_t *ctx, char **tag, + char **tag_val, int dodecode) { - char *tag = NULL; - char *tag_val = NULL; - char *parsed_string; - apr_table_t *env = r->subprocess_env; + if (!ctx->intern->argv) { + *tag = NULL; + *tag_val = NULL; - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0); - if (tag_val == NULL) { - if (tag == NULL) { - return 0; /* Reached the end of the string. */ - } - else { - return 1; /* tags must have values. */ - } - } - if (!strcmp(tag, "errmsg")) { - if (ctx->error_str_override == NULL) { - ctx->error_str_override = (char *)apr_palloc(ctx->pool, - MAX_STRING_LEN); - ctx->error_str = ctx->error_str_override; - } - ap_ssi_parse_string(r, ctx, tag_val, ctx->error_str_override, - MAX_STRING_LEN, 0); - } - else if (!strcmp(tag, "timefmt")) { - apr_time_t date = r->request_time; - if (ctx->time_str_override == NULL) { - ctx->time_str_override = (char *)apr_palloc(ctx->pool, - MAX_STRING_LEN); - ctx->time_str = ctx->time_str_override; - } - ap_ssi_parse_string(r, ctx, tag_val, ctx->time_str_override, - MAX_STRING_LEN, 0); - 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")) { - parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0); - decodehtml(parsed_string); - if (!strcmp(parsed_string, "bytes")) { - ctx->flags |= FLAG_SIZE_IN_BYTES; - } - else if (!strcmp(parsed_string, "abbrev")) { - ctx->flags &= FLAG_SIZE_ABBREV; - } - } - else { - apr_bucket *tmp_buck; + return; + } - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown parameter \"%s\" to tag config in %s", - tag, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); - } - } + *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); } - return 0; -} + return; +} static int find_file(request_rec *r, const char *directive, const char *tag, char *tag_val, apr_finfo_t *finfo) @@ -1529,19 +1585,22 @@ static int find_file(request_rec *r, const char *directive, const char *tag, apr_status_t rv = APR_SUCCESS; if (!strcmp(tag, "file")) { - /* XXX: Port to apr_filepath_merge - * be safe; only files in this directory or below allowed - */ - if (!is_only_below(tag_val)) { + char *newpath; + + /* be safe; only files in this directory or below allowed */ + rv = apr_filepath_merge(&newpath, NULL, tag_val, + APR_FILEPATH_NOTABOVEROOT | + APR_FILEPATH_SECUREROOTTEST | + APR_FILEPATH_NOTABSOLUTE, r->pool); + + if (!APR_STATUS_IS_SUCCESS(rv)) { error_fmt = "unable to access file \"%s\" " "in parsed file %s"; } else { - ap_getparents(tag_val); /* get rid of any nasties */ - /* 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(tag_val, r, NULL); + rr = ap_sub_req_lookup_file(newpath, r, NULL); if (rr->status == HTTP_OK && rr->finfo.filetype != 0) { to_send = rr->filename; @@ -1580,1720 +1639,1947 @@ static int find_file(request_rec *r, const char *directive, const char *tag, return 0; } else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unable to get information about \"%s\" " - "in parsed file %s", - tag_val, r->filename); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "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, - "unknown parameter \"%s\" to tag %s in %s", - tag, directive, r->filename); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter \"%s\" " + "to tag %s in %s", tag, directive, r->filename); return -1; } } -static int handle_fsize(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) +/* + * + */ +static apr_status_t handle_include(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) { - char *tag = NULL; - char *tag_val = NULL; - apr_finfo_t finfo; - apr_size_t s_len; - apr_bucket *tmp_buck; - char *parsed_string; - - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if (tag_val == NULL) { - if (tag == NULL) { - return 0; - } - else { - return 1; - } + request_rec *r = f->r; + + if (!ctx->argc) { + ap_log_rerror(APLOG_MARK, + (ctx->flags & SSI_FLAG_PRINTING) + ? APLOG_ERR : APLOG_WARNING, + 0, r, "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; + } + + while (1) { + char *tag = NULL; + char *tag_val = NULL; + request_rec *rr = NULL; + char *error_fmt = NULL; + char *parsed_string; + + 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")) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "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; + apr_status_t rv; + + /* be safe; only files in this directory or below allowed */ + rv = apr_filepath_merge(&newpath, NULL, tag_val, + APR_FILEPATH_NOTABOVEROOT | + APR_FILEPATH_SECUREROOTTEST | + APR_FILEPATH_NOTABSOLUTE, ctx->dpool); + + if (!APR_STATUS_IS_SUCCESS(rv)) { + error_fmt = "unable to include file \"%s\" in parsed file %s"; } else { - parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0); - if (!find_file(r, "fsize", tag, parsed_string, &finfo)) { - /* XXX: if we *know* we're going to have to copy the - * thing off of the stack anyway, why not palloc buff - * instead of sticking it on the stack; then we can just - * use a pool bucket and skip the copy - */ - char buff[50]; - - if (!(ctx->flags & FLAG_SIZE_IN_BYTES)) { - apr_strfsize(finfo.size, buff); - s_len = strlen (buff); - } - else { - int l, x, pos = 0; - char tmp_buff[50]; - - apr_snprintf(tmp_buff, sizeof(tmp_buff), - "%" APR_OFF_T_FMT, finfo.size); - l = strlen(tmp_buff); /* grrr */ - for (x = 0; x < l; x++) { - if (x && (!((l - x) % 3))) { - buff[pos++] = ','; - } - buff[pos++] = tmp_buff[x]; - } - buff[pos] = '\0'; - s_len = pos; - } + rr = ap_sub_req_lookup_file(newpath, r, f->next); + } + } + else { + rr = ap_sub_req_lookup_uri(parsed_string, r, f->next); + } + + if (!error_fmt && rr->status != HTTP_OK) { + error_fmt = "unable to include \"%s\" in parsed file %s"; + } + + 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"; + } + + if (!error_fmt) { + int founddupe = 0; + request_rec *p, *q; - tmp_buck = apr_bucket_heap_create(buff, s_len, NULL, - r->connection->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; + /* try to avoid recursive includes. We do this by walking + * up the r->main list of subrequests, and at each level + * walking back through any internal redirects. At each + * step, we compare the filenames and the URIs. + * + * The filename comparison catches a recursive include + * with an ever-changing URL, eg. + * + * which, although they would eventually be caught because + * we have a limit on the length of files, etc., can + * recurse for a while. + * + * The URI comparison catches the case where the filename + * is changed while processing the request, so the + * current name is never the same as any previous one. + * This can happen with "DocumentRoot /foo" when you + * request "/" on the server and it includes "/". + * This only applies to modules such as mod_dir that + * (somewhat improperly) mess with r->filename outside + * of a filename translation phase. + */ + for (p = r; p && !founddupe; p = p->main) { + for (q = p; q; q = q->prev) { + if ((q->filename && rr->filename && + (strcmp(q->filename, rr->filename) == 0)) || + ((*q->uri == '/') && + (strcmp(q->uri, rr->uri) == 0))) { + + founddupe = 1; + break; } } - else { - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - } } + + if (p) { + error_fmt = "Recursive include of \"%s\" in parsed file %s"; + } + } + + /* 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 && ap_run_sub_req(rr)) { + error_fmt = "unable to include \"%s\" in parsed file %s"; + } + + if (error_fmt) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error_fmt, tag_val, + r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); + } + + /* destroy the sub request */ + if (rr) { + ap_destroy_sub_req(rr); + } + + if (error_fmt) { + break; } } - return 0; + + return APR_SUCCESS; } -static int handle_flastmod(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, - apr_bucket *head_ptr, apr_bucket **inserted_head) +/* + * + */ +static apr_status_t handle_echo(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) { - char *tag = NULL; - char *tag_val = NULL; - apr_finfo_t finfo; - apr_size_t t_len; - apr_bucket *tmp_buck; - char *parsed_string; - - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if (tag_val == NULL) { - if (tag == NULL) { - return 0; - } - else { - return 1; + enum {E_NONE, E_URL, E_ENTITY} encode; + request_rec *r = f->r; + + if (!ctx->argc) { + ap_log_rerror(APLOG_MARK, + (ctx->flags & SSI_FLAG_PRINTING) + ? APLOG_ERR : APLOG_WARNING, + 0, r, "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; + } + + encode = E_ENTITY; + + 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) { + switch(encode) { + case E_NONE: + echo_text = val; + break; + case E_URL: + echo_text = ap_escape_uri(ctx->dpool, val); + break; + case E_ENTITY: + echo_text = ap_escape_html(ctx->dpool, val); + break; } + + e_len = strlen(echo_text); } else { - parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0); - if (!find_file(r, "flastmod", tag, parsed_string, &finfo)) { - char *t_val; - - t_val = ap_ht_time(r->pool, finfo.mtime, ctx->time_str, 0); - t_len = strlen(t_val); - - tmp_buck = apr_bucket_pool_create(t_val, t_len, r->pool, - r->connection->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; - } - } - else { - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - } + echo_text = ctx->intern->undefined_echo; + e_len = ctx->intern->undefined_echo_len; + } + + 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, "encoding")) { + if (!strcasecmp(tag_val, "none")) { + encode = E_NONE; + } + else if (!strcasecmp(tag_val, "url")) { + encode = E_URL; + } + else if (!strcasecmp(tag_val, "entity")) { + encode = E_ENTITY; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value " + "\"%s\" to parameter \"encoding\" of tag echo in " + "%s", tag_val, r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); + break; } } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter " + "\"%s\" in tag echo of %s", tag, r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); + break; + } } - return 0; + + return APR_SUCCESS; } -static int re_check(request_rec *r, include_ctx_t *ctx, - char *string, char *rexp) +/* + * + */ +static apr_status_t handle_config(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) { - regex_t *compiled; - const apr_size_t nres = sizeof(*ctx->re_result) / sizeof(regmatch_t); - int regex_error; + request_rec *r = f->r; + apr_table_t *env = r->subprocess_env; - compiled = ap_pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB); - if (compiled == NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unable to compile pattern \"%s\"", rexp); - return -1; + if (!ctx->argc) { + ap_log_rerror(APLOG_MARK, + (ctx->flags & SSI_FLAG_PRINTING) + ? APLOG_ERR : APLOG_WARNING, + 0, r, "missing argument for config element in %s", + r->filename); } - if (!ctx->re_result) { - ctx->re_result = apr_pcalloc(r->pool, sizeof(*ctx->re_result)); - } - ctx->re_string = string; - regex_error = ap_regexec(compiled, string, nres, *ctx->re_result, 0); - ap_pregfree(r->pool, compiled); - return (!regex_error); -} -enum token_type { - 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 -}; -struct token { - enum token_type type; - char* value; -}; + if (!(ctx->flags & SSI_FLAG_PRINTING)) { + return APR_SUCCESS; + } -static const char *get_ptoken(request_rec *r, const char *string, - struct token *token, int *unmatched) -{ - char ch; - int next = 0; - char qs = 0; - int tkn_fnd = 0; + if (!ctx->argc) { + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); + return APR_SUCCESS; + } - token->value = NULL; + while (1) { + char *tag = NULL; + char *tag_val = NULL; - /* Skip leading white space */ - if (string == (char *) NULL) { - return (char *) NULL; - } - while ((ch = *string++)) { - if (!apr_isspace(ch)) { + ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, SSI_VALUE_RAW); + if (!tag || !tag_val) { break; } - } - if (ch == '\0') { - return (char *) NULL; - } - token->type = token_string; /* the default type */ - switch (ch) { - case '(': - token->type = token_lbrace; - return (string); - case ')': - token->type = token_rbrace; - return (string); - case '=': - token->type = token_eq; - return (string); - case '!': - if (*string == '=') { - token->type = token_ne; - return (string + 1); - } - else { - token->type = token_not; - return (string); - } - case '\'': - /* already token->type == token_string */ - qs = '\''; - break; - case '/': - token->type = token_re; - qs = '/'; - break; - case '|': - if (*string == '|') { - token->type = token_or; - return (string + 1); - } - break; - case '&': - if (*string == '&') { - token->type = token_and; - return (string + 1); + if (!strcmp(tag, "errmsg")) { + ctx->error_str = ap_ssi_parse_string(ctx, tag_val, NULL, 0, + SSI_EXPAND_DROP_NAME); } - break; - case '>': - if (*string == '=') { - token->type = token_ge; - return (string + 1); - } - else { - token->type = token_gt; - return (string); + 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); } - case '<': - if (*string == '=') { - token->type = token_le; - return (string + 1); - } - else { - token->type = token_lt; - return (string); + 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)); } - default: - /* already token->type == token_string */ - break; - } - /* We should only be here if we are in a string */ - token->value = apr_palloc(r->pool, strlen(string) + 2); /* 2 for ch plus - trailing null */ - if (!qs) { - token->value[next++] = ch; - } + else if (!strcmp(tag, "sizefmt")) { + char *parsed_string; - /* - * I used the ++string throughout this section so that string - * ends up pointing to the next token and I can just return it - */ - for (ch = *string; ((ch != '\0') && (!tkn_fnd)); ch = *++string) { - if (ch == '\\') { - if ((ch = *++string) == '\0') { - tkn_fnd = 1; - } - else { - token->value[next++] = ch; + 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 (!qs) { - if (apr_isspace(ch)) { - tkn_fnd = 1; - } - else { - switch (ch) { - case '(': - case ')': - case '=': - case '!': - case '<': - case '>': - tkn_fnd = 1; - break; - case '|': - if (*(string + 1) == '|') { - tkn_fnd = 1; - } - break; - case '&': - if (*(string + 1) == '&') { - tkn_fnd = 1; - } - break; - } - if (!tkn_fnd) { - token->value[next++] = ch; - } - } + else if (!strcmp(parsed_string, "abbrev")) { + ctx->flags &= SSI_FLAG_SIZE_ABBREV; } else { - if (ch == qs) { - qs = 0; - tkn_fnd = 1; - string++; - } - else { - token->value[next++] = ch; - } + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown value " + "\"%s\" to parameter \"sizefmt\" of tag config " + "in %s", parsed_string, r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); + break; } } - if (tkn_fnd) { + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unknown parameter " + "\"%s\" to tag config in %s", tag, r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } } - /* If qs is still set, we have an unmatched quote */ - if (qs) { - *unmatched = 1; - next = 0; - } - token->value[next] = '\0'; - - return (string); + return APR_SUCCESS; } - -/* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1 - * characters long... +/* + * */ -static int parse_expr(request_rec *r, include_ctx_t *ctx, const char *expr, - int *was_error, int *was_unmatched, char *debug) +static apr_status_t handle_fsize(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) { - struct parse_node { - struct parse_node *left, *right, *parent; - struct token token; - int value, done; - } *root, *current, *new; - const char *parse; - char* buffer; - int retval = 0; - apr_size_t debug_pos = 0; - - debug[debug_pos] = '\0'; - *was_error = 0; - *was_unmatched = 0; - if ((parse = expr) == (char *) NULL) { - return (0); - } - root = current = (struct parse_node *) NULL; + request_rec *r = f->r; + + if (!ctx->argc) { + ap_log_rerror(APLOG_MARK, + (ctx->flags & SSI_FLAG_PRINTING) + ? APLOG_ERR : APLOG_WARNING, + 0, r, "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; + } - /* Create Parse Tree */ while (1) { - new = (struct parse_node *) apr_palloc(r->pool, - sizeof(struct parse_node)); - new->parent = new->left = new->right = (struct parse_node *) NULL; - new->done = 0; - if ((parse = get_ptoken(r, parse, &new->token, was_unmatched)) == - (char *) NULL) { + 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; } - switch (new->token.type) { - case token_string: -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], - " Token: string (%s)\n", - new->token.value); -#endif - if (current == (struct parse_node *) NULL) { - root = current = new; - 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 */ } - switch (current->token.type) { - case token_string: - current->token.value = apr_pstrcat(r->pool, - current->token.value, - current->token.value[0] ? " " : "", - new->token.value, - NULL); - - break; - case token_eq: - case token_ne: - case token_and: - case token_or: - case token_lbrace: - case token_not: - case token_ge: - case token_gt: - case token_le: - case token_lt: - new->parent = current; - current = current->right = new; - break; - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; + 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; + } + } - case token_re: -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], - " Token: regex (%s)\n", - new->token.value); -#endif - if (current == (struct parse_node *) NULL) { - root = current = new; - break; - } - switch (current->token.type) { - case token_eq: - case token_ne: - case token_and: - case token_or: - case token_lbrace: - case token_not: - new->parent = current; - current = current->right = new; - break; - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } + return APR_SUCCESS; +} + +/* + * + */ +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, "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; + } - case token_and: - case token_or: -#ifdef DEBUG_INCLUDE - memcpy (&debug[debug_pos], " Token: and/or\n", - sizeof (" Token: and/or\n")); - debug_pos += sizeof (" Token: and/or\n"); -#endif - if (current == (struct parse_node *) NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - /* Percolate upwards */ - while (current != (struct parse_node *) NULL) { - switch (current->token.type) { - case token_string: - case token_re: - case token_group: - case token_not: - case token_eq: - case token_ne: - case token_and: - case token_or: - case token_ge: - case token_gt: - case token_le: - case token_lt: - current = current->parent; - continue; - case token_lbrace: - break; - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - break; - } - if (current == (struct parse_node *) NULL) { - new->left = root; - new->left->parent = new; - new->parent = (struct parse_node *) NULL; - root = new; - } - else { - new->left = current->right; - current->right = new; - new->parent = current; - } - current = new; + 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; + } + } - case token_not: -#ifdef DEBUG_INCLUDE - memcpy(&debug[debug_pos], " Token: not\n", - sizeof(" Token: not\n")); - debug_pos += sizeof(" Token: not\n"); -#endif - if (current == (struct parse_node *) NULL) { - root = current = new; - break; - } - /* Percolate upwards */ - while (current != (struct parse_node *) NULL) { - switch (current->token.type) { - case token_not: - case token_eq: - case token_ne: - case token_and: - case token_or: - case token_lbrace: - case token_ge: - case token_gt: - case token_le: - case token_lt: - break; - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } + return APR_SUCCESS; +} + +/* + * + */ +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) + ? "too many arguments for if element in %s" + : "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, "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, "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)); + + expr_ret = parse_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; +} + +/* + * + */ +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) + ? "too many arguments for if element in %s" + : "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, "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, "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; + } + + expr_ret = parse_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; +} + +/* + * + */ +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, "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; +} + +/* + * + */ +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, "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; +} + +/* + * + */ +static apr_status_t handle_set(include_ctx_t *ctx, ap_filter_t *f, + apr_bucket_brigade *bb) +{ + char *var = NULL; + request_rec *r = f->r; + request_rec *sub = r->main; + apr_pool_t *p = r->pool; + + if (ctx->argc < 2) { + ap_log_rerror(APLOG_MARK, + (ctx->flags & SSI_FLAG_PRINTING) + ? APLOG_ERR : APLOG_WARNING, + 0, r, "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_DECODED); + + if (!tag || !tag_val) { + break; + } + + if (!strcmp(tag, "var")) { + var = ap_ssi_parse_string(ctx, tag_val, NULL, 0, + SSI_EXPAND_DROP_NAME); + } + else if (!strcmp(tag, "value")) { + char *parsed_string; + + if (!var) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "variable must " + "precede value in set directive in %s", + r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; } - if (current == (struct parse_node *) NULL) { - new->left = root; - new->left->parent = new; - new->parent = (struct parse_node *) NULL; - root = new; - } - else { - new->left = current->right; - current->right = new; - new->parent = current; - } - current = new; + + parsed_string = ap_ssi_parse_string(ctx, tag_val, NULL, 0, + SSI_EXPAND_DROP_NAME); + 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, "Invalid tag for set " + "directive in %s", r->filename); + SSI_CREATE_ERROR_BUCKET(ctx, f, bb); break; + } + } + + return APR_SUCCESS; +} + +/* + * + */ +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, "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; + char *key_val, *next; + apr_size_t k_len, v_len, kv_length; + + /* get key */ + key_text = ap_escape_html(ctx->dpool, elts[i].key); + k_len = strlen(key_text); + + /* get value */ + val_text = elts[i].val; + if (val_text == LAZY_VALUE) { + val_text = add_include_vars_lazy(r, elts[i].key); + } + val_text = ap_escape_html(ctx->dpool, elts[i].val); + v_len = strlen(val_text); + + /* assemble result */ + kv_length = k_len + v_len + sizeof("=\n"); + key_val = apr_palloc(ctx->pool, kv_length); + next = key_val; + + memcpy(next, key_text, k_len); + next += k_len; + *next++ = '='; + memcpy(next, val_text, v_len); + next += v_len; + *next++ = '\n'; + *next = 0; + + APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(key_val, kv_length-1, + ctx->pool, f->c->bucket_alloc)); + } + + 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; - case token_eq: - case token_ne: - case token_ge: - case token_gt: - case token_le: - case token_lt: -#ifdef DEBUG_INCLUDE - memcpy(&debug[debug_pos], " Token: eq/ne/ge/gt/le/lt\n", - sizeof(" Token: eq/ne/ge/gt/le/lt\n")); - debug_pos += sizeof(" Token: eq/ne/ge/gt/le/lt\n"); -#endif - if (current == (struct parse_node *) NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - /* Percolate upwards */ - while (current != (struct parse_node *) NULL) { - switch (current->token.type) { - case token_string: - case token_re: - case token_group: - current = current->parent; - continue; - case token_lbrace: - case token_and: - case token_or: - break; - case token_not: - case token_eq: - case token_ne: - case token_ge: - case token_gt: - case token_le: - case token_lt: - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - break; - } - if (current == (struct parse_node *) NULL) { - new->left = root; - new->left->parent = new; - new->parent = (struct parse_node *) NULL; - root = new; - } - else { - new->left = current->right; - current->right = new; - new->parent = current; - } - current = new; - break; + he = h + hl; - case token_rbrace: -#ifdef DEBUG_INCLUDE - memcpy (&debug[debug_pos], " Token: rbrace\n", - sizeof (" Token: rbrace\n")); - debug_pos += sizeof (" Token: rbrace\n"); -#endif - while (current != (struct parse_node *) NULL) { - if (current->token.type == token_lbrace) { - current->token.type = token_group; - break; - } - current = current->parent; - } - if (current == (struct parse_node *) NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Unmatched ')' in \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - break; + T = t->T; + x = t->x; + nl = t->pattern_len; - case token_lbrace: -#ifdef DEBUG_INCLUDE - memcpy (&debug[debug_pos], " Token: lbrace\n", - sizeof (" Token: lbrace\n")); - debug_pos += sizeof (" Token: lbrace\n"); -#endif - if (current == (struct parse_node *) NULL) { - root = current = new; + 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; } - /* Percolate upwards */ - while (current != (struct parse_node *) NULL) { - switch (current->token.type) { - case token_not: - case token_eq: - case token_ne: - case token_and: - case token_or: - case token_lbrace: - case token_ge: - case token_gt: - case token_le: - case token_lt: - break; - case token_string: - case token_re: - case token_group: - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; + if ((d & 1)) { + if (p != pi) { + skip = p; + } + else { + return p - h + 1; } - break; - } - if (current == (struct parse_node *) NULL) { - new->left = root; - new->left->parent = new; - new->parent = (struct parse_node *) NULL; - root = new; - } - else { - new->left = current->right; - current->right = new; - new->parent = current; } - current = new; - break; - default: - break; + 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; } } - /* Evaluate Parse Tree */ - current = root; - while (current != (struct parse_node *) NULL) { - switch (current->token.type) { - case token_string: -#ifdef DEBUG_INCLUDE - memcpy (&debug[debug_pos], " Evaluate string\n", - sizeof (" Evaluate string\n")); - debug_pos += sizeof (" Evaluate string\n"); -#endif - buffer = ap_ssi_parse_string(r, ctx, current->token.value, NULL, - MAX_STRING_LEN, 0); - current->token.value = buffer; - current->value = (current->token.value[0] != '\0'); - current->done = 1; - current = current->parent; - break; + ep = data + len; + do { + while (p < ep && *p != *intern->start_seq) { + ++p; + } - case token_re: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "No operator before regex of expr \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; + index = p - data; - case token_and: - case token_or: -#ifdef DEBUG_INCLUDE - memcpy(&debug[debug_pos], " Evaluate and/or\n", - sizeof(" Evaluate and/or\n")); - debug_pos += sizeof(" Evaluate and/or\n"); -#endif - if (current->left == (struct parse_node *) NULL || - current->right == (struct parse_node *) NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; + /* 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; } - if (!current->left->done) { - switch (current->left->token.type) { - case token_string: - buffer = ap_ssi_parse_string(r, ctx, current->left->token.value, - NULL, MAX_STRING_LEN, 0); - current->left->token.value = buffer; - current->left->value = - (current->left->token.value[0] != '\0'); - current->left->done = 1; - break; - default: - current = current->left; - continue; - } + + /* partial match found. Store the info for the next round */ + if (p == ep) { + intern->state = PARSE_HEAD; + intern->parse_pos = pos; + return index; } - if (!current->right->done) { - switch (current->right->token.type) { - case token_string: - buffer = ap_ssi_parse_string(r, ctx, current->right->token.value, - NULL, MAX_STRING_LEN, 0); - current->right->token.value = buffer; - current->right->value = - (current->right->token.value[0] != '\0'); - current->right->done = 1; - break; - default: - current = current->right; - continue; + } + + /* 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; } - } -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], " Left: %c\n", - current->left->value ? '1' : '0'); - debug_pos += sprintf (&debug[debug_pos], " Right: %c\n", - current->right->value ? '1' : '0'); -#endif - if (current->token.type == token_and) { - current->value = current->left->value && current->right->value; - } - else { - current->value = current->left->value || current->right->value; - } -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], " Returning %c\n", - current->value ? '1' : '0'); -#endif - current->done = 1; - current = current->parent; - break; - case token_eq: - case token_ne: -#ifdef DEBUG_INCLUDE - memcpy (&debug[debug_pos], " Evaluate eq/ne\n", - sizeof (" Evaluate eq/ne\n")); - debug_pos += sizeof (" Evaluate eq/ne\n"); -#endif - if ((current->left == (struct parse_node *) NULL) || - (current->right == (struct parse_node *) NULL) || - (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, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - buffer = ap_ssi_parse_string(r, ctx, current->left->token.value, - NULL, MAX_STRING_LEN, 0); - current->left->token.value = buffer; - buffer = ap_ssi_parse_string(r, ctx, current->right->token.value, - NULL, MAX_STRING_LEN, 0); - current->right->token.value = buffer; - if (current->right->token.type == token_re) { -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], - " Re Compare (%s) with /%s/\n", - current->left->token.value, - current->right->token.value); -#endif - current->value = - re_check(r, ctx, current->left->token.value, - current->right->token.value); - } - else { -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], - " Compare (%s) with (%s)\n", - current->left->token.value, - current->right->token.value); -#endif - current->value = - (strcmp(current->left->token.value, - current->right->token.value) == 0); - } - if (current->token.type == token_ne) { - current->value = !current->value; - } -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], " Returning %c\n", - current->value ? '1' : '0'); -#endif - current->done = 1; - current = current->parent; - break; - case token_ge: - case token_gt: - case token_le: - case token_lt: -#ifdef DEBUG_INCLUDE - memcpy (&debug[debug_pos], " Evaluate ge/gt/le/lt\n", - sizeof (" Evaluate ge/gt/le/lt\n")); - debug_pos += sizeof (" Evaluate ge/gt/le/lt\n"); -#endif - if ((current->left == (struct parse_node *) NULL) || - (current->right == (struct parse_node *) NULL) || - (current->left->token.type != token_string) || - (current->right->token.type != token_string)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Invalid expression \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; - } - buffer = ap_ssi_parse_string(r, ctx, current->left->token.value, - NULL, MAX_STRING_LEN, 0); - current->left->token.value = buffer; - buffer = ap_ssi_parse_string(r, ctx, current->right->token.value, - NULL, MAX_STRING_LEN, 0); - current->right->token.value = buffer; -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], - " Compare (%s) with (%s)\n", - current->left->token.value, - current->right->token.value); -#endif - current->value = - strcmp(current->left->token.value, - current->right->token.value); - if (current->token.type == token_ge) { - current->value = current->value >= 0; - } - else if (current->token.type == token_gt) { - current->value = current->value > 0; - } - else if (current->token.type == token_le) { - current->value = current->value <= 0; - } - else if (current->token.type == token_lt) { - current->value = current->value < 0; - } - else { - current->value = 0; /* Don't return -1 if unknown token */ - } -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], " Returning %c\n", - current->value ? '1' : '0'); -#endif - current->done = 1; - current = current->parent; - break; + /* if a matching beginning char was found, try to match the + * remainder of the old buffer. + */ + if (pos > 1) { + apr_size_t t = 1; - case token_not: - if (current->right != (struct parse_node *) NULL) { - if (!current->right->done) { - current = current->right; - continue; + ++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; + } } - current->value = !current->right->value; + } while (pos > 1); + + if (pos) { + continue; } - else { - current->value = 0; + } + + 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. + */ + if (*p == *intern->end_seq) { + intern->state = PARSE_DIRECTIVE_TAIL; + intern->parse_pos = 1; + ++p; + return (p - data); } -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], " Evaluate !: %c\n", - current->value ? '1' : '0'); -#endif - current->done = 1; - current = current->parent; + ++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; + } - case token_group: - if (current->right != (struct parse_node *) NULL) { - if (!current->right->done) { - current = current->right; - continue; - } - current->value = current->right->value; - } - else { - current->value = 1; - } -#ifdef DEBUG_INCLUDE - debug_pos += sprintf (&debug[debug_pos], " Evaluate (): %c\n", - current->value ? '1' : '0'); -#endif - current->done = 1; - current = current->parent; + /* partial match, the buffer is too small to match fully */ + if (p == ep) { + intern->parse_pos = pos; break; + } - case token_lbrace: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Unmatched '(' in \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; + /* 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 token_rbrace: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "Unmatched ')' in \"%s\" in file %s", - expr, r->filename); - *was_error = 1; - return retval; + case PARSE_DIRECTIVE_POSTNAME: + if (PARSE_DIRECTIVE_POSTNAME == intern->state) { + intern->state = PARSE_PRE_ARG; + } + ctx->argc = 0; + intern->argv = NULL; - default: - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "bad token type"); - *was_error = 1; - return retval; + if (!intern->directive_len) { + intern->error = 1; + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, intern->r, "missing " + "directive name in parsed document %s", + intern->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; } - retval = (root == (struct parse_node *) NULL) ? 0 : root->value; - return (retval); + return (p - data); } -/*-------------------------------------------------------------------------*/ -#ifdef DEBUG_INCLUDE +/* + * 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; -#define MAX_DEBUG_SIZE MAX_STRING_LEN -#define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text) \ -{ \ - char cond_txt[] = "**** X conditional_status=\"0\"\n"; \ - \ - if (cntx->flags & FLAG_COND_TRUE) { \ - cond_txt[31] = '1'; \ - } \ - memcpy(&cond_txt[5], tag_text, sizeof(tag_text)-1); \ - t_buck = apr_bucket_heap_create(cond_txt, sizeof(cond_txt)-1, \ - NULL, h_ptr->list); \ - APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck); \ - \ - if (ins_head == NULL) { \ - ins_head = t_buck; \ - } \ -} -#define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head) \ -{ \ - if (d_buf[0] != '\0') { \ - t_buck = apr_bucket_heap_create(d_buf, strlen(d_buf), \ - NULL, h_ptr->list); \ - APR_BUCKET_INSERT_BEFORE(h_ptr, t_buck); \ - \ - if (ins_head == NULL) { \ - ins_head = t_buck; \ - } \ - } \ -} -#else + /* skip leading WS */ + while (p < ep && apr_isspace(*p)) { + ++p; + } -#define MAX_DEBUG_SIZE 10 -#define LOG_COND_STATUS(cntx, t_buck, h_ptr, ins_head, tag_text) -#define DUMP_PARSE_EXPR_DEBUG(t_buck, h_ptr, d_buf, ins_head) + /* buffer doesn't consist of whitespaces only */ + if (p < ep) { + intern->state = (*p == *intern->end_seq) ? PARSE_TAIL : PARSE_ARG; + } -#endif -/*-------------------------------------------------------------------------*/ + return (p - data); +} -/* pjr - These seem to allow expr="fred" expr="joe" where joe overwrites fred. */ -static int handle_if(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) +/* + * 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) { - char *tag = NULL; - char *tag_val = NULL; - char *expr = NULL; - int expr_ret, was_error, was_unmatched; - apr_bucket *tmp_buck; - char debug_buf[MAX_DEBUG_SIZE]; + struct ssi_internal_ctx *intern = ctx->intern; + const char *p = data; + const char *ep = data + len; + apr_size_t pos = intern->parse_pos; - *inserted_head = NULL; - if (!(ctx->flags & FLAG_PRINTING)) { - ctx->if_nesting_level++; + if (PARSE_TAIL == intern->state) { + intern->state = PARSE_TAIL_SEQ; + pos = intern->parse_pos = 0; } - else { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0); - if (tag == NULL) { - if (expr == NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "missing expr in if statement: %s", - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - return 1; - } - expr_ret = parse_expr(r, ctx, expr, &was_error, - &was_unmatched, debug_buf); - if (was_error) { - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - return 1; - } - if (was_unmatched) { - DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, - "\nUnmatched '\n", *inserted_head); - } - DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, - *inserted_head); - - if (expr_ret) { - ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE); - } - else { - ctx->flags &= FLAG_CLEAR_PRINT_COND; - } - LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, - " if"); - ctx->if_nesting_level = 0; - return 0; - } - else if (!strcmp(tag, "expr")) { - expr = tag_val; -#ifdef DEBUG_INCLUDE - if (1) { - apr_size_t d_len = 0; - d_len = sprintf(debug_buf, "**** if expr=\"%s\"\n", expr); - tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL, - r->connection->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; - } - } -#endif - } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown parameter \"%s\" to tag if in %s", tag, - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); - } - } + 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; } -static int handle_elif(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) +/* + * 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) { - char *tag = NULL; - char *tag_val = NULL; - char *expr = NULL; - int expr_ret, was_error, was_unmatched; - apr_bucket *tmp_buck; - char debug_buf[MAX_DEBUG_SIZE]; - - *inserted_head = NULL; - if (!ctx->if_nesting_level) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 0); - if (tag == '\0') { - LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, - " elif"); - - if (ctx->flags & FLAG_COND_TRUE) { - ctx->flags &= FLAG_CLEAR_PRINTING; - return (0); - } - if (expr == NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "missing expr in elif statement: %s", - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - return (1); - } - expr_ret = parse_expr(r, ctx, expr, &was_error, - &was_unmatched, debug_buf); - if (was_error) { - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - return 1; - } - if (was_unmatched) { - DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, - "\nUnmatched '\n", *inserted_head); - } - DUMP_PARSE_EXPR_DEBUG(tmp_buck, head_ptr, debug_buf, - *inserted_head); - - if (expr_ret) { - ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE); - } - else { - ctx->flags &= FLAG_CLEAR_PRINT_COND; - } - LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, - " elif"); - return (0); + 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; } - else if (!strcmp(tag, "expr")) { - expr = tag_val; -#ifdef DEBUG_INCLUDE - if (1) { - apr_size_t d_len = 0; - d_len = sprintf(debug_buf, "**** elif expr=\"%s\"\n", expr); - tmp_buck = apr_bucket_heap_create(debug_buf, d_len, NULL, - r->connection->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; - } - } -#endif + 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, intern->r, "missing " + "argument name for value to tag %s in %s", + apr_pstrmemdup(intern->r->pool, intern->directive, + intern->directive_len), + intern->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, intern->r, "missing " + "argument name for value to tag %s in %s", + apr_pstrmemdup(intern->r->pool, intern->directive, + intern->directive_len), + intern->r->filename); + } + else { + char *sp = intern->current_arg->name; + + /* normalize the name */ + while (*sp) { + *sp = apr_tolower(*sp); + ++sp; } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown parameter \"%s\" to tag if in %s", tag, - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); + } + + 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); } - } - return 0; -} + break; -static int handle_else(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) -{ - char *tag = NULL; - char *tag_val = NULL; - apr_bucket *tmp_buck; + case PARSE_ARG_PREVAL: + *store = NULL; - *inserted_head = NULL; - if (!ctx->if_nesting_level) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if ((tag != NULL) || (tag_val != NULL)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "else directive does not take tags in %s", r->filename); - if (ctx->flags & FLAG_PRINTING) { - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); + 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 -1; + + return (p - data); } - else { - LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, " else"); - - if (ctx->flags & FLAG_COND_TRUE) { - ctx->flags &= FLAG_CLEAR_PRINTING; + 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 { - ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE); + 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 0; } - } - return 0; -} -static int handle_endif(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) -{ - char *tag = NULL; - char *tag_val = NULL; - apr_bucket *tmp_buck; - - *inserted_head = NULL; - if (!ctx->if_nesting_level) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if ((tag != NULL) || (tag_val != NULL)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "endif directive does not take tags in %s", r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); - return -1; - } - else { - LOG_COND_STATUS(ctx, tmp_buck, head_ptr, *inserted_head, "endif"); - ctx->flags |= (FLAG_PRINTING | FLAG_COND_TRUE); - return 0; + return (p - data); + + case PARSE_ARG_POSTVAL: + /* + * The value is still the raw input string. Finally clean it up. + */ + --(intern->current_arg->value_len); + intern->current_arg->value[intern->current_arg->value_len] = '\0'; + + /* 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; } - } - else { - ctx->if_nesting_level--; + + 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 */ } -static int handle_set(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, apr_bucket *head_ptr, - apr_bucket **inserted_head) +/* + * 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) { - char *tag = NULL; - char *tag_val = NULL; - char *var = NULL; - apr_bucket *tmp_buck; - char *parsed_string; - request_rec *sub = r->main; - apr_pool_t *p = r->pool; + 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 */ - /* 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; + /* fast exit */ + if (APR_BRIGADE_EMPTY(bb)) { + return APR_SUCCESS; } - *inserted_head = NULL; - if (ctx->flags & FLAG_PRINTING) { - while (1) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if ((tag == NULL) && (tag_val == NULL)) { - return 0; - } - else if (tag_val == NULL) { - return 1; - } - else if (!strcmp(tag, "var")) { - var = ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0); - } - else if (!strcmp(tag, "value")) { - if (var == (char *) NULL) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "variable must precede value in set directive in %s", - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, - *inserted_head); - return (-1); + /* 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; + intern->r = r; + 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; + + /* 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 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 */ } - parsed_string = ap_ssi_parse_string(r, ctx, tag_val, NULL, - MAX_STRING_LEN, 0); - 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, - "Invalid tag for set directive in %s", r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); - return -1; + APR_BRIGADE_INSERT_TAIL(pass_bb, b); + + if (APR_BUCKET_IS_FLUSH(b)) { + ctx->flush_now = 1; + } + + b = newb; + continue; } } - } - return 0; -} -static int handle_printenv(include_ctx_t *ctx, apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f, - apr_bucket *head_ptr, apr_bucket **inserted_head) -{ - char *tag = NULL; - char *tag_val = NULL; - apr_bucket *tmp_buck; - - if (ctx->flags & FLAG_PRINTING) { - ap_ssi_get_tag_and_value(ctx, &tag, &tag_val, 1); - if ((tag == NULL) && (tag_val == NULL)) { - const apr_array_header_t *arr = apr_table_elts(r->subprocess_env); - const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts; - int i; - const char *key_text, *val_text; - char *key_val, *next; - apr_size_t k_len, v_len, kv_length; - - *inserted_head = NULL; - for (i = 0; i < arr->nelts; ++i) { - key_text = ap_escape_html(r->pool, elts[i].key); - val_text = elts[i].val; - if (val_text == LAZY_VALUE) { - val_text = add_include_vars_lazy(r, elts[i].key); + /* 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 (!APR_STATUS_IS_SUCCESS(rv)) { + apr_brigade_destroy(pass_bb); + return rv; } - val_text = ap_escape_html(r->pool, elts[i].val); - k_len = strlen(key_text); - v_len = strlen(val_text); - kv_length = k_len + v_len + sizeof("=\n"); - key_val = apr_palloc(r->pool, kv_length); - next = key_val; - memcpy(next, key_text, k_len); - next += k_len; - *next++ = '='; - memcpy(next, val_text, v_len); - next += v_len; - *next++ = '\n'; - *next = 0; - tmp_buck = apr_bucket_pool_create(key_val, kv_length - 1, - r->pool, - r->connection->bucket_alloc); - APR_BUCKET_INSERT_BEFORE(head_ptr, tmp_buck); - if (*inserted_head == NULL) { - *inserted_head = tmp_buck; + } + + 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; } } - return 0; + + if (!len || !APR_STATUS_IS_SUCCESS(rv)) { + rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); + } + + if (!APR_STATUS_IS_SUCCESS(rv)) { + apr_brigade_destroy(pass_bb); + return rv; + } + + intern->bytes_read += len; } - else { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "printenv directive does not take tags in %s", - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_buck, head_ptr, *inserted_head); - return -1; + + /* zero length bucket, fetch next one */ + if (!len && !intern->seen_eos) { + b = APR_BUCKET_NEXT(b); + continue; } - } - return 0; -} -/* -------------------------- The main function --------------------------- */ + /* + * it's actually a data containing bucket, start/continue parsing + */ -static apr_status_t send_parsed_content(apr_bucket_brigade **bb, - request_rec *r, ap_filter_t *f) -{ - include_ctx_t *ctx = f->ctx; - apr_bucket *dptr = APR_BRIGADE_FIRST(*bb); - apr_bucket *tmp_dptr; - apr_bucket_brigade *tag_and_after; - apr_status_t rv = APR_SUCCESS; + switch (intern->state) { + /* no current tag; search for start sequence */ + case PARSE_PRE_HEAD: + index = find_start_sequence(ctx, data, len); - if (r->args) { /* add QUERY stuff to env cause it ain't yet */ - char *arg_copy = apr_pstrdup(r->pool, r->args); + if (index < len) { + apr_bucket_split(b, index); + } - 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)); - } + 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); + } - while (dptr != APR_BRIGADE_SENTINEL(*bb) && !APR_BUCKET_IS_EOS(dptr)) { - /* State to check for the STARTING_SEQUENCE. */ - if ((ctx->state == PRE_HEAD) || (ctx->state == PARSE_HEAD)) { - int do_cleanup = 0; - apr_size_t cleanup_bytes = ctx->parse_pos; + 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 */ + } - tmp_dptr = find_start_sequence(dptr, ctx, *bb, &do_cleanup); - if (!APR_STATUS_IS_SUCCESS(ctx->status)) { - return ctx->status; + b = APR_BUCKET_NEXT(newb); + apr_bucket_delete(newb); + } + else { + b = newb; } - /* The few bytes stored in the ssi_tag_brigade turned out not to - * be a tag after all. This can only happen if the starting - * tag actually spans brigades. This should be very rare. - */ - if ((do_cleanup) && (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade))) { - apr_bucket *tmp_bkt; + break; - tmp_bkt = apr_bucket_immortal_create(ctx->start_seq, - cleanup_bytes, - r->connection->bucket_alloc); - APR_BRIGADE_INSERT_HEAD(*bb, tmp_bkt); - apr_brigade_cleanup(ctx->ssi_tag_brigade); + /* 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_palloc(ctx->pool, release); + + memcpy(to_release, 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 I am inside a conditional (if, elif, else) that is false - * then I need to throw away anything contained in it. - */ - if ((!(ctx->flags & FLAG_PRINTING)) && (tmp_dptr != NULL) && - (dptr != APR_BRIGADE_SENTINEL(*bb))) { - while ((dptr != APR_BRIGADE_SENTINEL(*bb)) && - (dptr != tmp_dptr)) { - apr_bucket *free_bucket = dptr; - - dptr = APR_BUCKET_NEXT (dptr); - apr_bucket_delete(free_bucket); + 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); } - /* Adjust the current bucket position based on what was found... */ - if ((tmp_dptr != NULL) && (ctx->state == PARSE_DIRECTIVE)) { - if (ctx->tag_start_bucket != NULL) { - dptr = ctx->tag_start_bucket; + if (store) { + if (index) { + APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); + b = newb; } - else { - dptr = APR_BRIGADE_SENTINEL(*bb); + + /* 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 ((tmp_dptr != NULL) && - (ctx->output_now || - (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD))) { - /* Send the large chunk of pre-tag bytes... */ - tag_and_after = apr_brigade_split(*bb, tmp_dptr); - if (ctx->output_flush) { - APR_BRIGADE_INSERT_TAIL(*bb, apr_bucket_flush_create((*bb)->bucket_alloc)); - } + else if (index) { + apr_bucket_delete(b); + b = newb; + } - rv = ap_pass_brigade(f->next, *bb); - if (rv != APR_SUCCESS) { - return rv; + 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); } - *bb = tag_and_after; - dptr = tmp_dptr; - ctx->output_flush = 0; - ctx->bytes_parsed = 0; - ctx->output_now = 0; - } - else if (tmp_dptr == NULL) { - /* There was no possible SSI tag in the - * remainder of this brigade... */ - dptr = APR_BRIGADE_SENTINEL(*bb); - } - } - - /* State to check for the ENDING_SEQUENCE. */ - if (((ctx->state == PARSE_DIRECTIVE) || - (ctx->state == PARSE_TAG) || - (ctx->state == PARSE_TAIL)) && - (dptr != APR_BRIGADE_SENTINEL(*bb))) { - tmp_dptr = find_end_sequence(dptr, ctx, *bb); - if (!APR_STATUS_IS_SUCCESS(ctx->status)) { - return ctx->status; - } - - if (tmp_dptr != NULL) { - dptr = tmp_dptr; /* Adjust bucket pos... */ - - /* If some of the tag has already been set aside then set - * aside remainder of tag. Now the full tag is in - * ssi_tag_brigade. - * If none has yet been set aside, then leave it all where it - * is. - * In any event after this the entire set of tag buckets will - * be in one place or another. - */ - if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) { - tag_and_after = apr_brigade_split(*bb, dptr); - APR_BRIGADE_CONCAT(ctx->ssi_tag_brigade, *bb); - *bb = tag_and_after; + 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_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); + b = newb; } - else if (ctx->output_now || - (ctx->bytes_parsed >= BYTE_COUNT_THRESHOLD)) { - SPLIT_AND_PASS_PRETAG_BUCKETS(*bb, ctx, f->next, rv); - if (rv != APR_SUCCESS) { - return rv; - } - ctx->output_flush = 0; - ctx->output_now = 0; + + /* time for cleanup? */ + if (store != &magic) { + apr_brigade_pflatten(intern->tmp_bb, store, store_len, + ctx->dpool); + apr_brigade_cleanup(intern->tmp_bb); } } - else { - /* remainder of this brigade... */ - dptr = APR_BRIGADE_SENTINEL(*bb); + else if (index) { + apr_bucket_delete(b); + b = newb; } - } - /* State to processed the directive... */ - if (ctx->state == PARSED) { - apr_bucket *content_head = NULL, *tmp_bkt; - apr_size_t tmp_i; - char tmp_buf[TMP_BUF_SIZE]; - int (*handle_func)(include_ctx_t *, apr_bucket_brigade **, - request_rec *, ap_filter_t *, apr_bucket *, - apr_bucket **); + break; - /* By now the full tag (all buckets) should either be set aside into - * ssi_tag_brigade or contained within the current bb. All tag - * processing from here on can assume that. - */ + /* 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; - /* At this point, everything between ctx->head_start_bucket and - * ctx->tail_start_bucket is an SSI - * directive, we just have to deal with it now. - */ - if (get_combined_directive(ctx, r, *bb, tmp_buf, - TMP_BUF_SIZE) != APR_SUCCESS) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "mod_include: error copying directive in %s", - r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head); - - /* DO CLEANUP HERE!!!!! */ - tmp_dptr = ctx->head_start_bucket; - if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) { - apr_brigade_cleanup(ctx->ssi_tag_brigade); - } - else { - do { - tmp_bkt = tmp_dptr; - tmp_dptr = APR_BUCKET_NEXT (tmp_dptr); - apr_bucket_delete(tmp_bkt); - } while ((tmp_dptr != dptr) && - (tmp_dptr != APR_BRIGADE_SENTINEL(*bb))); - } + 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; - return APR_SUCCESS; + default: /* partial match */ + newb = APR_BUCKET_NEXT(b); + APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(intern->tmp_bb, b); + b = newb; + break; } - /* Can't destroy the tag buckets until I'm done processing - * because the combined_tag might just be pointing to - * the contents of a single bucket! - */ + break; - /* Retrieve the handler function to be called for this directive - * from the functions registered in the hash table. - * Need to lower case the directive for proper matching. Also need - * to have it NULL terminated for proper hash matching. - */ - for (tmp_i = 0; tmp_i < ctx->directive_length; tmp_i++) { - ctx->combined_tag[tmp_i] = - apr_tolower(ctx->combined_tag[tmp_i]); - } - ctx->combined_tag[ctx->directive_length] = '\0'; - ctx->curr_tag_pos = &ctx->combined_tag[ctx->directive_length+1]; - - handle_func = - (include_handler_fn_t *)apr_hash_get(include_hash, - ctx->combined_tag, - ctx->directive_length); - if (handle_func != NULL) { - rv = (*handle_func)(ctx, bb, r, f, dptr, &content_head); - if ((rv != 0) && (rv != 1)) { - return (rv); + /* 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 { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, - "unknown directive \"%s\" in parsed doc %s", - ctx->combined_tag, r->filename); - CREATE_ERROR_BUCKET(ctx, tmp_bkt, dptr, content_head); - } + include_handler_fn_t *handle_func; - /* This chunk of code starts at the first bucket in the chain - * of tag buckets (assuming that by this point the bucket for - * the STARTING_SEQUENCE has been split) and loops through to - * the end of the tag buckets freeing them all. - * - * Remember that some part of this may have been set aside - * into the ssi_tag_brigade and the remainder (possibly as - * little as one byte) will be in the current brigade. - * - * The value of dptr should have been set during the - * PARSE_TAIL state to the first bucket after the - * ENDING_SEQUENCE. - * - * The value of content_head may have been set during processing - * of the directive. If so, the content was inserted in front - * of the dptr bucket. The inserted buckets should not be thrown - * away here, but they should also not be parsed later. - */ - if (content_head == NULL) { - content_head = dptr; - } - tmp_dptr = ctx->head_start_bucket; - if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) { - apr_brigade_cleanup(ctx->ssi_tag_brigade); - } - else { - do { - tmp_bkt = tmp_dptr; - tmp_dptr = APR_BUCKET_NEXT (tmp_dptr); - apr_bucket_delete(tmp_bkt); - } while ((tmp_dptr != content_head) && - (tmp_dptr != APR_BRIGADE_SENTINEL(*bb))); - } - if (ctx->combined_tag == tmp_buf) { - ctx->combined_tag = NULL; + handle_func = 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 (!APR_STATUS_IS_SUCCESS(rv)) { + apr_brigade_destroy(pass_bb); + return rv; + } + } + else { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "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); + } + } } - /* Don't reset the flags or the nesting level!!! */ - ctx->parse_pos = 0; - ctx->head_start_bucket = NULL; - ctx->head_start_index = 0; - ctx->tag_start_bucket = NULL; - ctx->tag_start_index = 0; - ctx->tail_start_bucket = NULL; - ctx->tail_start_index = 0; - ctx->curr_tag_pos = NULL; - ctx->tag_length = 0; - ctx->directive_length = 0; + /* cleanup */ + apr_pool_clear(ctx->dpool); + apr_brigade_cleanup(intern->tmp_bb); - if (!APR_BRIGADE_EMPTY(ctx->ssi_tag_brigade)) { - apr_brigade_cleanup(ctx->ssi_tag_brigade); - } + /* Oooof. Done here, start next round */ + intern->state = PARSE_PRE_HEAD; + break; - ctx->state = PRE_HEAD; - } - } + } /* switch(ctx->state) */ - /* We have nothing more to send, stop now. */ - if (dptr != APR_BRIGADE_SENTINEL(*bb) && - APR_BUCKET_IS_EOS(dptr)) { - /* We might have something saved that we never completed, but send - * down unparsed. This allows for