declare it with the default type AP_FTYPE_RESOURCE. The provider
must have been
registered with <code>ap_register_output_filter</code> by some module.
- The remaining arguments to <directive module="mod_filter"
- >FilterProvider</directive> are a dispatch criterion and a match string.
- The former may be an HTTP request or response header, an environment
- variable, or the Handler used by this request. The latter is matched
- to it for each request, to determine whether this provider will be
- used to implement the filter for this request.</dd>
+ The final argument to <directive module="mod_filter"
+ >FilterProvider</directive> is an expression: the provider will be
+ selected to run for a request if and only if the expression evaluates
+ to true. The expression may evaluate HTTP request or response
+ headers, environment variables, or the Handler used by this request.
+ Unlike earlier versions, mod_filter now supports complex expressions
+ involving multiple criteria with AND / OR logic (&& / ||)
+ and brackets.</dd>
<dt>Configure the Chain</dt>
<dd>The above directives build components of a smart filter chain,
<directive module="core">AddOutputFilterByType</directive>
<example>
FilterDeclare SSI<br/>
- FilterProvider SSI INCLUDES resp=Content-Type $text/html<br/>
+ FilterProvider SSI INCLUDES "$resp{Content-Type} = /^text\/html/"<br/>
FilterChain SSI
</example>
</dd>
<dd>The same as the above but dispatching on handler (classic
SSI behaviour; .shtml files get processed).
<example>
- FilterProvider SSI INCLUDES Handler server-parsed<br/>
+ FilterProvider SSI INCLUDES "Handler = server-parsed"<br/>
FilterChain SSI
</example>
</dd>
Accept-Encoding header. This filter runs with ftype CONTENT_SET.
<example>
FilterDeclare gzip CONTENT_SET<br/>
- FilterProvider gzip inflate req=Accept-Encoding !$gzip<br/>
+ FilterProvider gzip inflate "$req{Accept-Encoding} != /gzip/"<br/>
FilterChain gzip
</example>
</dd>
<dd>Suppose we want to downsample all web images, and have filters
for GIF, JPEG and PNG.
<example>
- FilterProvider unpack jpeg_unpack Content-Type $image/jpeg<br/>
- FilterProvider unpack gif_unpack Content-Type $image/gif<br/>
- FilterProvider unpack png_unpack Content-Type $image/png<br/>
+ FilterProvider unpack jpeg_unpack "$resp{Content-Type} = image/jpeg"<br/>
+ FilterProvider unpack gif_unpack "$resp{Content-Type} = image/gif"<br/>
+ FilterProvider unpack png_unpack "$resp{Content-Type} = image/png"<br/>
<br />
- FilterProvider downsample downsample_filter Content-Type $image<br/>
+ FilterProvider downsample downsample_filter "$resp{Content-Type} = /image\/(jpeg|gif|png)/"<br/>
FilterProtocol downsample "change=yes"<br/>
<br />
- FilterProvider repack jpeg_pack Content-Type $image/jpeg<br/>
- FilterProvider repack gif_pack Content-Type $image/gif<br/>
- FilterProvider repack png_pack Content-Type $image/png<br/>
+ FilterProvider repack jpeg_pack "$resp{Content-Type} = image/jpeg"<br/>
+ FilterProvider repack gif_pack "$resp{Content-Type} = image/gif"<br/>
+ FilterProvider repack png_pack "$resp{Content-Type} = image/png"<br/>
<Location /image-filter><br/>
<indent>
FilterChain unpack downsample repack<br/>
<name>FilterProvider</name>
<description>Register a content filter</description>
<syntax>FilterProvider <var>filter-name</var> <var>provider-name</var>
- [req|resp|env]=<var>dispatch</var> <var>match</var></syntax>
+ <var>expression</var></syntax>
<contextlist><context>server config</context><context>virtual host</context>
<context>directory</context><context>.htaccess</context></contextlist>
<override>Options</override>
<usage>
<p>This directive registers a <em>provider</em> for the smart filter.
- The provider will be called if and only if the <var>match</var> declared
- here matches the value of the header or environment variable declared
- as <var>dispatch</var>.</p>
+ The provider will be called if and only if the <var>expression</var>
+ declared evaluates to true when the harness is first called.</p>
<p>
<var>provider-name</var> must have been registered by loading
a module that registers the name with
<code>ap_register_output_filter</code>.
-<!-- Placeholder; this is totally broken as of now
- , or declared with
- <directive module="mod_filter">FilterDeclare</directive>
- (or implicitly with another
- <directive module="mod_filter">FilterProvider</directive>).
--->
</p>
- <p>The <var>dispatch</var> argument is a string with optional
- <code>req=</code>, <code>resp=</code> or <code>env=</code> prefix
- causing it to dispatch on (respectively) the request header, response
- header, or environment variable named. In the absence of a
- prefix, it defaults to a response header. A special case is the
- word <code>handler</code>, which causes <module>mod_filter</module>
- to dispatch on the content handler.</p>
-
- <p>The <var>match</var> argument specifies a match that will be applied to
- the filter's <var>dispatch</var> criterion. The <var>match</var> may be
- a string match (exact match or substring), a <glossary ref="regex"
- >regex</glossary>, an integer (greater, lessthan or equals), or
- unconditional. The first characters of the <var>match</var> argument
- determines this:</p>
-
- <p><strong>First</strong>, if the first character is an exclamation mark
- (<code>!</code>), this reverses the rule, so the provider will be used
- if and only if the match <em>fails</em>.</p>
-
- <p><strong>Second</strong>, it interprets the first character excluding
- any leading <code>!</code> as follows:</p>
-
- <table style="zebra" border="yes">
- <tr><th>Character</th><th>Description</th></tr>
- <tr><td><em>(none)</em></td><td>exact match</td></tr>
- <tr><td><code>$</code></td><td>substring match</td></tr>
- <tr><td><code>/</code></td><td>regex match (delimited by a second <code>/</code>)</td></tr>
- <tr><td><code>=</code></td><td>integer equality</td></tr>
- <tr><td><code><</code></td><td>integer less-than</td></tr>
- <tr><td><code><=</code></td><td>integer less-than or equal</td></tr>
- <tr><td><code>></code></td><td>integer greater-than</td></tr>
- <tr><td><code>>=</code></td><td>integer greater-than or equal</td></tr>
- <tr><td><code>*</code></td><td>Unconditional match</td></tr>
- </table>
+ <p><var>expression</var> can be any of the following:</p>
+ <dl>
+ <dt><code><var>string</var></code></dt>
+ <dd>true if <var>string</var> is not empty</dd>
+
+ <dt><code><var>string1</var> = <var>string2</var><br />
+ <var>string1</var> == <var>string2</var><br />
+ <var>string1</var> != <var>string2</var></code></dt>
+
+ <dd><p>Compare <var>string1</var> with <var>string2</var>. If
+ <var>string2</var> has the form <code>/<var>string2</var>/</code>
+ then it is treated as a regular expression. Regular expressions are
+ implemented by the <a href="http://www.pcre.org">PCRE</a> engine and
+ have the same syntax as those in <a href="http://www.perl.com">perl
+ 5</a>. Note that <code>==</code> is just an alias for <code>=</code>
+ and behaves exactly the same way.</p>
+ </dd>
+
+ <dt><code><var>string1</var> < <var>string2</var><br />
+ <var>string1</var> <= <var>string2</var><br />
+ <var>string1</var> > <var>string2</var><br />
+ <var>string1</var> >= <var>string2</var></code></dt>
+
+ <dd>Compare <var>string1</var> with <var>string2</var>. Note, that
+ strings are compared <em>literally</em> (using
+ <code>strcmp(3)</code>). Therefore the string "100" is less than
+ "20".</dd>
+
+ <dt><code>( <var>expression</var> )</code></dt>
+ <dd>true if <var>expression</var> is true</dd>
+
+ <dt><code>! <var>expression</var></code></dt>
+ <dd>true if <var>expression</var> is false</dd>
+
+ <dt><code><var>expression1</var> &&
+ <var>expression2</var></code></dt>
+ <dd>true if both <var>expression1</var> and
+ <var>expression2</var> are true</dd>
+
+ <dt><code><var>expression1</var> ||
+ <var>expression2</var></code></dt>
+ <dd>true if either <var>expression1</var> or
+ <var>expression2</var> is true</dd>
+ </dl>
+
</usage>
</directivesynopsis>
#include "http_request.h"
#include "http_log.h"
#include "util_filter.h"
+#include "ap_expr.h"
module AP_MODULE_DECLARE_DATA filter_module;
* (2.0-compatible) ap_filter_rec_t* frec.
*/
struct ap_filter_provider_t {
- /** How to match this provider to filter dispatch criterion */
- enum {
- STRING_MATCH,
- STRING_CONTAINS,
- REGEX_MATCH,
- INT_EQ,
- INT_LT,
- INT_LE,
- INT_GT,
- INT_GE,
- DEFINED
- } match_type;
-
- /** negation on match_type */
- int not;
-
- /** The dispatch match itself - union member depends on match_type */
- union {
- const char *string;
- ap_regex_t *regex;
- int number;
- } match;
+ ap_parse_node_t *expr;
/** The filter that implements this provider */
ap_filter_rec_t *frec;
/** The next provider in the list */
ap_filter_provider_t *next;
-
- /** Dispatch criteria for filter providers */
- enum {
- HANDLER,
- REQUEST_HEADERS,
- RESPONSE_HEADERS,
- SUBPROCESS_ENV,
- CONTENT_TYPE
- } dispatch;
-
- /** Match value for filter providers */
- const char* value;
};
/** we need provider_ctx to save ctx values set by providers in filter_init */
f->ctx = fctx;
return OK;
}
-
static int filter_lookup(ap_filter_t *f, ap_filter_rec_t *filter)
{
ap_filter_provider_t *provider;
const char *str = NULL;
char *str1;
int match;
+ int err = 0;
unsigned int proto_flags;
request_rec *r = f->r;
harness_ctx *ctx = f->ctx;
provider_ctx *pctx;
+ ap_parse_node_t *tree;
mod_filter_ctx *rctx = ap_get_module_config(r->request_config,
&filter_module);
/* Check registered providers in order */
for (provider = filter->providers; provider; provider = provider->next) {
- match = 1;
- switch (provider->dispatch) {
- case REQUEST_HEADERS:
- str = apr_table_get(r->headers_in, provider->value);
- break;
- case RESPONSE_HEADERS:
- str = apr_table_get(r->headers_out, provider->value);
- break;
- case SUBPROCESS_ENV:
- str = apr_table_get(r->subprocess_env, provider->value);
- break;
- case CONTENT_TYPE:
- str = r->content_type;
- break;
- case HANDLER:
- str = r->handler;
- break;
+ tree = ap_expr_clone_tree(r->pool, provider->expr, NULL);
+ match = ap_expr_eval(r, tree, &err, NULL, ap_expr_string, NULL);
+ if (err) {
+ /* log error but accept match value ? */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+ "Error evaluating filter dispatch condition");
}
- /* treat nulls so we don't have to check every strcmp individually
- * Not sure if there's anything better to do with them
- */
- if (!str) {
- if (provider->match_type == DEFINED && provider->match.string) {
- match = 0;
- }
- }
- /* we can't check for NULL in provider as that kills integer 0
- * so we have to test each string/regexp case in the switch
- */
- else {
- switch (provider->match_type) {
- case STRING_MATCH:
- if (strcasecmp(str, provider->match.string)) {
- match = 0;
- }
- break;
- case STRING_CONTAINS:
- str1 = apr_pstrdup(r->pool, str);
- ap_str_tolower(str1);
- if (!strstr(str1, provider->match.string)) {
- match = 0;
- }
- break;
- case REGEX_MATCH:
- if (ap_regexec(provider->match.regex, str, 0, NULL, 0)
- == AP_REG_NOMATCH) {
- match = 0;
- }
- break;
- case INT_EQ:
- if (atoi(str) != provider->match.number) {
- match = 0;
- }
- break;
- /* Integer comparisons should be [var] OP [match]
- * We need to set match = 0 if the condition fails
- */
- case INT_LT:
- if (atoi(str) >= provider->match.number) {
- match = 0;
- }
- break;
- case INT_LE:
- if (atoi(str) > provider->match.number) {
- match = 0;
- }
- break;
- case INT_GT:
- if (atoi(str) <= provider->match.number) {
- match = 0;
- }
- break;
- case INT_GE:
- if (atoi(str) < provider->match.number) {
- match = 0;
- }
- break;
- case DEFINED: /* we already handled this:-) */
- break;
- }
- }
-
- if (match != provider->not) {
+ if (match) {
/* condition matches this provider */
#ifndef NO_PROTOCOL
/* check protocol
return NULL;
}
-static const char *filter_provider(cmd_parms *cmd, void *CFG, const char *args)
+static const char *filter_provider(cmd_parms *cmd, void *CFG,
+ const char *fname, const char *pname,
+ const char *expr)
{
mod_filter_cfg *cfg = CFG;
- int flags;
ap_filter_provider_t *provider;
- const char *rxend;
const char *c;
- char *str;
- const char *eq;
ap_filter_rec_t* frec;
ap_filter_rec_t* provider_frec;
-
- /* insist on exactly four arguments */
- const char *fname = ap_getword_conf(cmd->pool, &args) ;
- const char *pname = ap_getword_conf(cmd->pool, &args) ;
- const char *condition = ap_getword_conf(cmd->pool, &args) ;
- const char *match = ap_getword_conf(cmd->pool, &args) ;
- eq = ap_getword_conf(cmd->pool, &args) ;
- if ( !*fname || !*pname || !*match || !*condition || *eq ) {
- return "usage: FilterProvider filter provider condition match" ;
- }
+ ap_parse_node_t *node;
+ int err = 0;
/* fname has been declared with DeclareFilter, so we can look it up */
frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
if (!provider_frec) {
return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname);
}
-
- provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t));
- if (*match == '!') {
- provider->not = 1;
- ++match;
- }
- else {
- provider->not = 0;
+ node = ap_expr_parse(cmd->pool, expr, &err);
+ if (err) {
+ return "Error parsing FilterProvider expression.";
}
- switch (*match++) {
- case '<':
- if (*match == '=') {
- provider->match_type = INT_LE;
- ++match;
- }
- else {
- provider->match_type = INT_LT;
- }
- provider->match.number = atoi(match);
- break;
- case '>':
- if (*match == '=') {
- provider->match_type = INT_GE;
- ++match;
- }
- else {
- provider->match_type = INT_GT;
- }
- provider->match.number = atoi(match);
- break;
- case '=':
- provider->match_type = INT_EQ;
- provider->match.number = atoi(match);
- break;
- case '/':
- provider->match_type = REGEX_MATCH;
- rxend = ap_strchr_c(match, '/');
- if (!rxend) {
- return "Bad regexp syntax";
- }
- flags = AP_REG_NOSUB; /* we're not mod_rewrite:-) */
- for (c = rxend+1; *c; ++c) {
- switch (*c) {
- case 'i': flags |= AP_REG_ICASE; break;
- }
- }
- provider->match.regex = ap_pregcomp(cmd->pool,
- apr_pstrndup(cmd->pool,
- match,
- rxend-match),
- flags);
- if (provider->match.regex == NULL) {
- return "Bad regexp";
- }
- break;
- case '*':
- provider->match_type = DEFINED;
- provider->match.number = -1;
- break;
- case '$':
- provider->match_type = STRING_CONTAINS;
- str = apr_pstrdup(cmd->pool, match);
- ap_str_tolower(str);
- provider->match.string = str;
- break;
- default:
- provider->match_type = STRING_MATCH;
- provider->match.string = apr_pstrdup(cmd->pool, match-1);
- break;
- }
+ provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t));
+ provider->expr = node;
provider->frec = provider_frec;
provider->next = frec->providers;
frec->providers = provider;
- /* determine what a filter will dispatch this provider on */
- eq = ap_strchr_c(condition, '=');
- if (eq) {
- str = apr_pstrdup(cmd->pool, eq+1);
- if (!strncasecmp(condition, "env=", 4)) {
- provider->dispatch = SUBPROCESS_ENV;
- }
- else if (!strncasecmp(condition, "req=", 4)) {
- provider->dispatch = REQUEST_HEADERS;
- }
- else if (!strncasecmp(condition, "resp=", 5)) {
- provider->dispatch = RESPONSE_HEADERS;
- }
- else {
- return "FilterProvider: unrecognized dispatch table";
- }
- }
- else {
- if (!strcasecmp(condition, "handler")) {
- provider->dispatch = HANDLER;
- }
- else {
- provider->dispatch = RESPONSE_HEADERS;
- }
- str = apr_pstrdup(cmd->pool, condition);
- ap_str_tolower(str);
- }
-
- if ( (provider->dispatch == RESPONSE_HEADERS)
- && !strcasecmp(str, "content-type")) {
- provider->dispatch = CONTENT_TYPE;
- }
- provider->value = str;
-
return NULL;
}
AP_INIT_TAKE12("FilterDeclare", filter_declare, NULL, OR_OPTIONS,
"filter-name [filter-type]"),
/** we don't have a TAKE4, so we have to use RAW_ARGS */
- AP_INIT_RAW_ARGS("FilterProvider", filter_provider, NULL, OR_OPTIONS,
- "filter-name provider-name dispatch-criterion dispatch-match"),
+ AP_INIT_TAKE3("FilterProvider", filter_provider, NULL, OR_OPTIONS,
+ "filter-name provider-name match-expression"),
AP_INIT_ITERATE("FilterChain", filter_chain, NULL, OR_OPTIONS,
"list of filter names with optional [+-=!@]"),
AP_INIT_TAKE2("FilterTrace", filter_debug, NULL, RSRC_CONF | ACCESS_CONF,