From 6b670e344e5b3538a134fa3c3b10c0ade50a36ef Mon Sep 17 00:00:00 2001
From: Nick Kew
Date: Mon, 31 Mar 2008 13:14:02 +0000
Subject: [PATCH] Update mod_filter to use ap_expr 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 | 126 ++++++++--------
modules/filters/mod_filter.c | 260 +++------------------------------
2 files changed, 89 insertions(+), 297 deletions(-)
diff --git a/docs/manual/mod/mod_filter.xml b/docs/manual/mod/mod_filter.xml
index f754e68074..7d373a4890 100644
--- a/docs/manual/mod/mod_filter.xml
+++ b/docs/manual/mod/mod_filter.xml
@@ -111,12 +111,14 @@
declare it with the default type AP_FTYPE_RESOURCE. The provider
must have been
registered with ap_register_output_filter
by some module.
- The remaining arguments to FilterProvider 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.
+ The final argument to FilterProvider 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.
Configure the Chain
The above directives build components of a smart filter chain,
@@ -134,7 +136,7 @@
AddOutputFilterByType
FilterDeclare SSI
- FilterProvider SSI INCLUDES resp=Content-Type $text/html
+ FilterProvider SSI INCLUDES "$resp{Content-Type} = /^text\/html/"
FilterChain SSI
@@ -143,7 +145,7 @@
The same as the above but dispatching on handler (classic
SSI behaviour; .shtml files get processed).
- FilterProvider SSI INCLUDES Handler server-parsed
+ FilterProvider SSI INCLUDES "Handler = server-parsed"
FilterChain SSI
@@ -153,7 +155,7 @@
Accept-Encoding header. This filter runs with ftype CONTENT_SET.
FilterDeclare gzip CONTENT_SET
- FilterProvider gzip inflate req=Accept-Encoding !$gzip
+ FilterProvider gzip inflate "$req{Accept-Encoding} != /gzip/"
FilterChain gzip
@@ -162,16 +164,16 @@
Suppose we want to downsample all web images, and have filters
for GIF, JPEG and PNG.
- FilterProvider unpack jpeg_unpack Content-Type $image/jpeg
- FilterProvider unpack gif_unpack Content-Type $image/gif
- FilterProvider unpack png_unpack Content-Type $image/png
+ FilterProvider unpack jpeg_unpack "$resp{Content-Type} = image/jpeg"
+ FilterProvider unpack gif_unpack "$resp{Content-Type} = image/gif"
+ FilterProvider unpack png_unpack "$resp{Content-Type} = image/png"
- FilterProvider downsample downsample_filter Content-Type $image
+ FilterProvider downsample downsample_filter "$resp{Content-Type} = /image\/(jpeg|gif|png)/"
FilterProtocol downsample "change=yes"
- FilterProvider repack jpeg_pack Content-Type $image/jpeg
- FilterProvider repack gif_pack Content-Type $image/gif
- FilterProvider repack png_pack Content-Type $image/png
+ FilterProvider repack jpeg_pack "$resp{Content-Type} = image/jpeg"
+ FilterProvider repack gif_pack "$resp{Content-Type} = image/gif"
+ FilterProvider repack png_pack "$resp{Content-Type} = image/png"
<Location /image-filter>
FilterChain unpack downsample repack
@@ -252,63 +254,67 @@
FilterProvider
Register a content filter
FilterProvider filter-name provider-name
- [req|resp|env]=dispatch match
+ expression
server configvirtual host
directory.htaccess
Options
This directive registers a provider for the smart filter.
- The provider will be called if and only if the match declared
- here matches the value of the header or environment variable declared
- as dispatch.
+ The provider will be called if and only if the expression
+ declared evaluates to true when the harness is first called.
provider-name must have been registered by loading
a module that registers the name with
ap_register_output_filter
.
-
- The dispatch argument is a string with optional
- req=
, resp=
or env=
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 handler
, which causes mod_filter
- to dispatch on the content handler.
-
- The match argument specifies a match that will be applied to
- the filter's dispatch criterion. The match may be
- a string match (exact match or substring), a regex, an integer (greater, lessthan or equals), or
- unconditional. The first characters of the match argument
- determines this:
-
- First, if the first character is an exclamation mark
- (!
), this reverses the rule, so the provider will be used
- if and only if the match fails.
-
- Second, it interprets the first character excluding
- any leading !
as follows:
-
-
- Character | Description |
- (none) | exact match |
- $ | substring match |
- / | regex match (delimited by a second / ) |
- = | integer equality |
- < | integer less-than |
- <= | integer less-than or equal |
- > | integer greater-than |
- >= | integer greater-than or equal |
- * | Unconditional match |
-
+ expression can be any of the following:
+
+ string
+ - true if string is not empty
+
+ string1 = string2
+ string1 == string2
+ string1 != string2
+
+ Compare string1 with string2. If
+ string2 has the form /string2/
+ then it is treated as a regular expression. Regular expressions are
+ implemented by the PCRE engine and
+ have the same syntax as those in perl
+ 5. Note that ==
is just an alias for =
+ and behaves exactly the same way.
+
+
+ string1 < string2
+ string1 <= string2
+ string1 > string2
+ string1 >= string2
+
+ - Compare string1 with string2. Note, that
+ strings are compared literally (using
+
strcmp(3)
). Therefore the string "100" is less than
+ "20".
+
+ ( expression )
+ - true if expression is true
+
+ ! expression
+ - true if expression is false
+
+ expression1 &&
+ expression2
+ - true if both expression1 and
+ expression2 are true
+
+ expression1 ||
+ expression2
+ - true if either expression1 or
+ expression2 is true
+
+
diff --git a/modules/filters/mod_filter.c b/modules/filters/mod_filter.c
index d3c5ce16a7..b1bdd0e40e 100644
--- a/modules/filters/mod_filter.c
+++ b/modules/filters/mod_filter.c
@@ -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,
--
2.40.0