]> granicus.if.org Git - apache/commitdiff
Update mod_filter to use ap_expr
authorNick Kew <niq@apache.org>
Mon, 31 Mar 2008 13:14:02 +0000 (13:14 +0000)
committerNick Kew <niq@apache.org>
Mon, 31 Mar 2008 13:14:02 +0000 (13:14 +0000)
Advantage: supports more complex expressions while simplifying mod_filter
(c.f. PR 43956 - this means chaining is no longer necessary)
NOTE: this changes FilterProvider syntax, so can't be backported to 2.2.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@642983 13f79535-47bb-0310-9956-ffa450edef68

docs/manual/mod/mod_filter.xml
modules/filters/mod_filter.c

index f754e6807489c1d77075d5c44f18a31ffceb907f..7d373a489059338a87c3b39f270beed2cb700a72 100644 (file)
     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 (&amp;&amp; / ||)
+    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/>
       &lt;Location /image-filter&gt;<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>&lt;</code></td><td>integer less-than</td></tr>
-    <tr><td><code>&lt;=</code></td><td>integer less-than or equal</td></tr>
-    <tr><td><code>&gt;</code></td><td>integer greater-than</td></tr>
-    <tr><td><code>&gt;=</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> &lt; <var>string2</var><br />
+       <var>string1</var> &lt;= <var>string2</var><br />
+       <var>string1</var> &gt; <var>string2</var><br />
+       <var>string1</var> &gt;= <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> &amp;&amp;
+        <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>
 
index d3c5ce16a790770954efa18cdb312e13e4020114..b1bdd0e40eee968d85617a4bff28e2406f437afb 100644 (file)
@@ -24,6 +24,7 @@
 #include "http_request.h"
 #include "http_log.h"
 #include "util_filter.h"
+#include "ap_expr.h"
 
 module AP_MODULE_DECLARE_DATA filter_module;
 
@@ -35,46 +36,13 @@ 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 */
@@ -162,106 +130,32 @@ static int filter_init(ap_filter_t *f)
     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
@@ -497,27 +391,17 @@ static const char *filter_declare(cmd_parms *cmd, void *CFG, const char *fname,
     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);
@@ -540,115 +424,17 @@ static const char *filter_provider(cmd_parms *cmd, void *CFG, const char *args)
     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;
 }
 
@@ -851,8 +637,8 @@ static const command_rec filter_cmds[] = {
     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,