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:

- - - - - - - - - - - - -
CharacterDescription
(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