1 /* Copyright 2004 The Apache Software Foundation
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
16 /* Originally contributed by Nick Kew <nick webthing.com>
18 * At the time of writing, this is designed primarily for use with
19 * httpd 2.2, but is also back-compatible with 2.0. It is likely
20 * that the 2.0 and 2.2 versions may diverge in future, as additional
21 * capabilities for 2.2 are added, including updates to util_filter.
23 * 21/9/04: Unifying data structures with util_filter.
24 * From now on, until and unless we backport, mod_filter requires
25 * util_filter.h from CVS or httpd-2.1+ to compile.
26 * There's a minimal patch for httpd-2.0 users maintained by Nick
27 * to compile mod_filter at http://www.apache.org/~niq/
30 #define APR_WANT_STRFUNC
33 #include "apr_strings.h"
36 #include "http_config.h"
37 #include "http_request.h"
39 #include "util_filter.h"
41 module AP_MODULE_DECLARE_DATA filter_module;
44 ap_out_filter_func func;
48 typedef struct mod_filter_chain {
50 struct mod_filter_chain *next;
54 apr_hash_t *live_filters;
55 mod_filter_chain *chain;
58 static const char *filter_bucket_type(apr_bucket *b)
61 const apr_bucket_type_t *fn;
64 { &apr_bucket_type_heap, "HEAP" },
65 { &apr_bucket_type_transient, "TRANSIENT" },
66 { &apr_bucket_type_immortal, "IMMORTAL" },
67 { &apr_bucket_type_pool, "POOL" },
68 { &apr_bucket_type_eos, "EOS" },
69 { &apr_bucket_type_flush, "FLUSH" },
70 { &apr_bucket_type_file, "FILE" },
72 { &apr_bucket_type_mmap, "MMAP" },
74 { &apr_bucket_type_pipe, "PIPE" },
75 { &apr_bucket_type_socket, "SOCKET" },
81 if (b->type == types[i].fn) {
84 } while (types[++i].fn != NULL);
89 static void filter_trace(apr_pool_t *pool, int debug, const char *fname,
90 apr_bucket_brigade *bb)
94 case 0: /* normal, operational use */
96 case 1: /* mod_diagnostics level */
97 ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, pool, fname);
98 for (b = APR_BRIGADE_FIRST(bb);
99 b != APR_BRIGADE_SENTINEL(bb);
100 b = APR_BUCKET_NEXT(b)) {
101 ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, pool, " %s: %s %d",
102 fname, filter_bucket_type(b), b->length);
108 static int filter_init(ap_filter_t *f)
110 ap_filter_provider_t *p;
112 ap_filter_rec_t *filter = f->frec;
114 f->ctx = apr_pcalloc(f->r->pool, sizeof(harness_ctx));
115 for (p = filter->providers; p; p = p->next) {
116 if (p->frec->filter_init_func) {
117 if (err = p->frec->filter_init_func(f), err != OK) {
118 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
119 "filter_init for %s failed", p->frec->name);
120 break; /* if anyone errors out here, so do we */
128 static ap_out_filter_func filter_lookup(request_rec *r, ap_filter_rec_t *filter)
130 ap_filter_provider_t *provider;
131 const char *str = NULL;
134 unsigned int proto_flags;
136 /* Check registered providers in order */
137 for (provider = filter->providers; provider; provider = provider->next) {
139 switch (filter->dispatch) {
140 case REQUEST_HEADERS:
141 str = apr_table_get(r->headers_in, filter->value);
143 case RESPONSE_HEADERS:
144 str = apr_table_get(r->headers_out, filter->value);
147 str = apr_table_get(r->subprocess_env, filter->value);
150 str = r->content_type;
157 /* treat nulls so we don't have to check every strcmp individually
158 * Not sure if there's anything better to do with them
161 if (provider->match_type == DEFINED && provider->match.c) {
165 else if (!provider->match.c) {
169 /* Now we have no nulls, so we can do string and regexp matching */
170 switch (provider->match_type) {
172 if (strcasecmp(str, provider->match.c)) {
176 case STRING_CONTAINS:
177 str1 = apr_pstrdup(r->pool, str);
178 ap_str_tolower(str1);
179 if (!strstr(str1, provider->match.c)) {
184 if (ap_regexec(provider->match.r, str, 0, NULL, 0)
190 if (atoi(str) != provider->match.i) {
195 if (atoi(str) < provider->match.i) {
200 if (atoi(str) > provider->match.i) {
204 case DEFINED: /* we already handled this:-) */
209 if (match != provider->not) {
210 /* condition matches this provider */
215 * This is a quick hack and almost certainly buggy.
216 * The idea is that by putting this in mod_filter, we relieve
217 * filter implementations of the burden of fixing up HTTP headers
218 * for cases that are routinely affected by filters.
220 * Default is ALWAYS to do nothing, so as not to tread on the
221 * toes of filters which want to do it themselves.
224 proto_flags = filter->proto_flags | provider->frec->proto_flags;
226 /* some specific things can't happen in a proxy */
228 if (proto_flags & AP_FILTER_PROTO_NO_PROXY) {
229 /* can't use this provider; try next */
233 if (proto_flags & AP_FILTER_PROTO_TRANSFORM) {
234 str = apr_table_get(r->headers_out, "Cache-Control");
236 str1 = apr_pstrdup(r->pool, str);
237 ap_str_tolower(str1);
238 if (strstr(str1, "no-transform")) {
239 /* can't use this provider; try next */
243 apr_table_addn(r->headers_out, "Warning",
244 apr_psprintf(r->pool,
245 "214 %s Transformation applied",
250 /* things that are invalidated if the filter transforms content */
251 if (proto_flags & AP_FILTER_PROTO_CHANGE) {
252 apr_table_unset(r->headers_out, "Content-MD5");
253 apr_table_unset(r->headers_out, "ETag");
254 if (proto_flags & AP_FILTER_PROTO_CHANGE_LENGTH) {
255 apr_table_unset(r->headers_out, "Content-Length");
259 /* no-cache is for a filter that has different effect per-hit */
260 if (proto_flags & AP_FILTER_PROTO_NO_CACHE) {
261 apr_table_unset(r->headers_out, "Last-Modified");
262 apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
265 if (proto_flags & AP_FILTER_PROTO_NO_BYTERANGE) {
266 apr_table_unset(r->headers_out, "Accept-Ranges");
268 else if (filter->range) {
269 apr_table_setn(r->headers_in, "Range", filter->range);
272 return provider->frec->filter_func.out_func;
276 /* No provider matched */
280 static apr_status_t filter_harness(ap_filter_t *f, apr_bucket_brigade *bb)
283 const char *cachecontrol;
285 harness_ctx *ctx = f->ctx;
286 ap_filter_rec_t *filter = f->frec;
288 if (f->r->status != 200) {
289 ap_remove_output_filter(f);
290 return ap_pass_brigade(f->next, bb);
293 filter_trace(f->c->pool, filter->debug, f->frec->name, bb);
295 /* look up a handler function if we haven't already set it */
298 if (f->r->proxyreq) {
299 if (filter->proto_flags & AP_FILTER_PROTO_NO_PROXY) {
300 ap_remove_output_filter(f);
301 return ap_pass_brigade(f->next, bb);
304 if (filter->proto_flags & AP_FILTER_PROTO_TRANSFORM) {
305 cachecontrol = apr_table_get(f->r->headers_out,
308 str = apr_pstrdup(f->r->pool, cachecontrol);
310 if (strstr(str, "no-transform")) {
311 ap_remove_output_filter(f);
312 return ap_pass_brigade(f->next, bb);
318 ctx->func = filter_lookup(f->r, filter);
320 ap_remove_output_filter(f);
321 return ap_pass_brigade(f->next, bb);
325 /* call the content filter with its own context, then restore our
329 ret = ctx->func(f, bb);
337 static const char *filter_protocol(cmd_parms *cmd, void *CFG, const char *fname,
338 const char *pname, const char *proto)
340 static const char *sep = ";,\t";
343 unsigned int flags = 0;
344 mod_filter_cfg *cfg = CFG;
345 ap_filter_provider_t *provider = NULL;
346 ap_filter_rec_t *filter = apr_hash_get(cfg->live_filters, fname,
347 APR_HASH_KEY_STRING);
350 return "FilterProtocol: No such filter";
353 /* Fixup the args: it's really pname that's optional */
360 for (provider = filter->providers; provider; provider = provider->next) {
361 if (!strcasecmp(provider->frec->name, pname)) {
366 return "FilterProtocol: No such provider for this filter";
370 /* Now set flags from our args */
371 for (arg = apr_strtok(apr_pstrdup(cmd->pool, proto), sep, &tok);
372 arg; arg = apr_strtok(NULL, sep, &tok)) {
374 if (!strcasecmp(arg, "change=yes")) {
375 flags |= AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH;
377 else if (!strcasecmp(arg, "change=1:1")) {
378 flags |= AP_FILTER_PROTO_CHANGE;
380 else if (!strcasecmp(arg, "byteranges=no")) {
381 flags |= AP_FILTER_PROTO_NO_BYTERANGE;
383 else if (!strcasecmp(arg, "proxy=no")) {
384 flags |= AP_FILTER_PROTO_NO_PROXY;
386 else if (!strcasecmp(arg, "proxy=transform")) {
387 flags |= AP_FILTER_PROTO_TRANSFORM;
389 else if (!strcasecmp(arg, "cache=no")) {
390 flags |= AP_FILTER_PROTO_NO_CACHE;
395 provider->frec->proto_flags = flags;
398 filter->proto_flags = flags;
405 static const char *filter_declare(cmd_parms *cmd, void *CFG, const char *fname,
406 const char *condition, const char *place)
410 mod_filter_cfg *cfg = (mod_filter_cfg *)CFG;
411 ap_filter_rec_t *filter;
413 filter = apr_pcalloc(cmd->pool, sizeof(ap_filter_rec_t));
414 apr_hash_set(cfg->live_filters, fname, APR_HASH_KEY_STRING, filter);
416 filter->name = fname;
417 filter->filter_init_func = filter_init;
418 filter->filter_func.out_func = filter_harness;
419 filter->ftype = AP_FTYPE_RESOURCE;
422 /* determine what this filter will dispatch on */
423 eq = ap_strchr_c(condition, '=');
425 tmpname = apr_pstrdup(cmd->pool, eq+1);
426 if (!strncasecmp(condition, "env=", 4)) {
427 filter->dispatch = SUBPROCESS_ENV;
429 else if (!strncasecmp(condition, "req=", 4)) {
430 filter->dispatch = REQUEST_HEADERS;
432 else if (!strncasecmp(condition, "resp=", 5)) {
433 filter->dispatch = RESPONSE_HEADERS;
436 return "FilterCondition: unrecognized dispatch table";
440 if (!strcasecmp(condition, "handler")) {
441 filter->dispatch = HANDLER;
444 filter->dispatch = RESPONSE_HEADERS;
446 tmpname = apr_pstrdup(cmd->pool, condition);
447 ap_str_tolower(tmpname);
450 if ( (filter->dispatch == RESPONSE_HEADERS)
451 && !strcmp(tmpname, "content-type")) {
452 filter->dispatch = CONTENT_TYPE;
454 filter->value = tmpname;
457 if (!strcasecmp(place, "CONTENT_SET")) {
458 filter->ftype = AP_FTYPE_CONTENT_SET;
460 else if (!strcasecmp(place, "PROTOCOL")) {
461 filter->ftype = AP_FTYPE_PROTOCOL;
463 else if (!strcasecmp(place, "CONNECTION")) {
464 filter->ftype = AP_FTYPE_CONNECTION;
466 else if (!strcasecmp(place, "NETWORK")) {
467 filter->ftype = AP_FTYPE_NETWORK;
474 static const char *filter_provider(cmd_parms *cmd, void *CFG,
475 const char *fname, const char *pname, const char *match)
478 ap_filter_provider_t *provider;
483 /* fname has been declared with DeclareFilter, so we can look it up */
484 mod_filter_cfg *cfg = CFG;
485 ap_filter_rec_t *frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
486 /* if provider has been registered, we can look it up */
487 ap_filter_rec_t *provider_frec = ap_get_output_filter_handle(pname);
488 /* or if provider is mod_filter itself, we can also look it up */
490 if (!provider_frec) {
491 provider_frec = apr_hash_get(cfg->live_filters, pname, APR_HASH_KEY_STRING);
495 return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
497 else if (!provider_frec) {
498 return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname);
501 provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t));
503 if (match[0] == '!') {
513 provider->match_type = INT_LT;
514 provider->match.i = atoi(match+1);
517 provider->match_type = INT_GT;
518 provider->match.i = atoi(match+1);
521 provider->match_type = INT_EQ;
522 provider->match.i = atoi(match+1);
525 provider->match_type = REGEX_MATCH;
526 rxend = ap_strchr_c(match+1, '/');
528 return "Bad regexp syntax";
530 flags = REG_NOSUB; /* we're not mod_rewrite:-) */
531 for (c = rxend+1; *c; ++c) {
533 case 'i': flags |= REG_ICASE; break;
534 case 'x': flags |= REG_EXTENDED; break;
537 provider->match.r = ap_pregcomp(cmd->pool,
538 apr_pstrndup(cmd->pool, match+1,
543 provider->match_type = DEFINED;
544 provider->match.i = -1;
547 provider->match_type = STRING_CONTAINS;
548 str = apr_pstrdup(cmd->pool, match+1);
550 provider->match.c = str;
553 provider->match_type = STRING_MATCH;
554 provider->match.c = apr_pstrdup(cmd->pool, match);
557 provider->frec = provider_frec;
558 provider->next = frec->providers;
559 frec->providers = provider;
565 static const char *filter_chain(cmd_parms *cmd, void *CFG, const char *arg)
569 mod_filter_cfg *cfg = CFG;
572 case '+': /* add to end of chain */
573 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
576 for (q = cfg->chain; q->next; q = q->next);
584 case '@': /* add to start of chain */
585 p = apr_palloc(cmd->pool, sizeof(mod_filter_chain));
587 p->next = cfg->chain;
591 case '-': /* remove from chain */
593 if (strcasecmp(cfg->chain->fname, arg+1)) {
594 for (p = cfg->chain; p->next; p = p->next) {
595 if (!strcasecmp(p->next->fname, arg+1)) {
596 p->next = p->next->next;
601 cfg->chain = cfg->chain->next;
606 case '!': /* Empty the chain */
610 case '=': /* initialise chain with this arg */
611 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
616 default: /* add to end */
617 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
620 for (q = cfg->chain; q->next; q = q->next);
632 static const char *filter_debug(cmd_parms *cmd, void *CFG, const char *fname,
635 mod_filter_cfg *cfg = CFG;
636 ap_filter_rec_t *frec = apr_hash_get(cfg->live_filters, fname,
637 APR_HASH_KEY_STRING);
638 frec->debug = atoi(level);
643 static void filter_insert(request_rec *r)
646 ap_filter_rec_t *filter;
647 mod_filter_cfg *cfg = ap_get_module_config(r->per_dir_config,
653 for (p = cfg->chain; p; p = p->next) {
654 filter = apr_hash_get(cfg->live_filters, p->fname, APR_HASH_KEY_STRING);
655 ap_add_output_filter_handle(filter, NULL, r, r->connection);
657 if (ranges && (filter->proto_flags
658 & (AP_FILTER_PROTO_NO_BYTERANGE
659 | AP_FILTER_PROTO_CHANGE_LENGTH))) {
660 filter->range = apr_table_get(r->headers_in, "Range");
661 apr_table_unset(r->headers_in, "Range");
670 static void filter_hooks(apr_pool_t *pool)
672 ap_hook_insert_filter(filter_insert, NULL, NULL, APR_HOOK_MIDDLE);
675 static void *filter_config(apr_pool_t *pool, char *x)
677 mod_filter_cfg *cfg = apr_palloc(pool, sizeof(mod_filter_cfg));
678 cfg->live_filters = apr_hash_make(pool);
683 static void *filter_merge(apr_pool_t *pool, void *BASE, void *ADD)
685 mod_filter_cfg *base = BASE;
686 mod_filter_cfg *add = ADD;
687 mod_filter_chain *savelink = 0;
688 mod_filter_chain *newlink;
690 mod_filter_cfg *conf = apr_palloc(pool, sizeof(mod_filter_cfg));
692 conf->live_filters = apr_hash_overlay(pool, add->live_filters,
694 if (base->chain && add->chain) {
695 for (p = base->chain; p; p = p->next) {
696 newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
698 savelink->next = newlink;
702 conf->chain = savelink = newlink;
706 for (p = add->chain; p; p = p->next) {
707 newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
708 savelink->next = newlink;
712 else if (add->chain) {
713 conf->chain = add->chain;
716 conf->chain = base->chain;
722 static const command_rec filter_cmds[] = {
723 AP_INIT_TAKE23("FilterDeclare", filter_declare, NULL, OR_OPTIONS,
724 "filter-name, dispatch-criterion [, filter-type]"),
725 AP_INIT_TAKE3("FilterProvider", filter_provider, NULL, OR_OPTIONS,
726 "filter-name, provider-name, dispatch-match"),
727 AP_INIT_ITERATE("FilterChain", filter_chain, NULL, OR_OPTIONS,
728 "list of filter names with optional [+-=!@]"),
729 AP_INIT_TAKE2("FilterTrace", filter_debug, NULL, RSRC_CONF | ACCESS_CONF,
732 AP_INIT_TAKE23("FilterProtocol", filter_protocol, NULL, OR_OPTIONS,
733 "filter-name [provider-name] protocol-args"),
738 module AP_MODULE_DECLARE_DATA filter_module = {
739 STANDARD20_MODULE_STUFF,