1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #define APR_WANT_STRFUNC
20 #include "apr_strings.h"
23 #include "http_config.h"
24 #include "http_request.h"
26 #include "util_filter.h"
28 module AP_MODULE_DECLARE_DATA filter_module;
31 * @brief is a filter provider, as defined and implemented by mod_filter.
33 * The struct is a linked list, with dispatch criteria
34 * defined for each filter. The provider implementation itself is a
35 * (2.0-compatible) ap_filter_rec_t* frec.
37 struct ap_filter_provider_t {
38 /** How to match this provider to filter dispatch criterion */
51 /** negation on match_type */
54 /** The dispatch match itself - union member depends on match_type */
61 /** The filter that implements this provider */
62 ap_filter_rec_t *frec;
64 /** The next provider in the list */
65 ap_filter_provider_t *next;
67 /** Dispatch criteria for filter providers */
76 /** Match value for filter providers */
80 /** we need provider_ctx to save ctx values set by providers in filter_init */
81 typedef struct provider_ctx provider_ctx;
83 ap_filter_provider_t *provider;
88 ap_out_filter_func func;
90 provider_ctx *init_ctx;
93 typedef struct mod_filter_chain {
95 struct mod_filter_chain *next;
99 apr_hash_t *live_filters;
100 mod_filter_chain *chain;
108 static void filter_trace(conn_rec *c, int debug, const char *fname,
109 apr_bucket_brigade *bb)
114 case 0: /* normal, operational use */
116 case 1: /* mod_diagnostics level */
117 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "%s", fname);
118 for (b = APR_BRIGADE_FIRST(bb);
119 b != APR_BRIGADE_SENTINEL(bb);
120 b = APR_BUCKET_NEXT(b)) {
122 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
123 "%s: type: %s, length: %" APR_SIZE_T_FMT,
124 fname, b->type->name ? b->type->name : "(unknown)",
131 static int filter_init(ap_filter_t *f)
133 ap_filter_provider_t *p;
136 ap_filter_rec_t *filter = f->frec;
138 harness_ctx *fctx = apr_pcalloc(f->r->pool, sizeof(harness_ctx));
139 for (p = filter->providers; p; p = p->next) {
140 if (p->frec->filter_init_func) {
142 if ((err = p->frec->filter_init_func(f)) != OK) {
143 ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c,
144 "filter_init for %s failed", p->frec->name);
145 return err; /* if anyone errors out here, so do we */
147 if (f->ctx != NULL) {
148 /* the filter init function set a ctx - we need to record it */
149 pctx = apr_pcalloc(f->r->pool, sizeof(provider_ctx));
152 pctx->next = fctx->init_ctx;
153 fctx->init_ctx = pctx;
161 static int filter_lookup(ap_filter_t *f, ap_filter_rec_t *filter)
163 ap_filter_provider_t *provider;
164 const char *str = NULL;
167 unsigned int proto_flags;
168 request_rec *r = f->r;
169 harness_ctx *ctx = f->ctx;
171 mod_filter_ctx *rctx = ap_get_module_config(r->request_config,
174 /* Check registered providers in order */
175 for (provider = filter->providers; provider; provider = provider->next) {
177 switch (provider->dispatch) {
178 case REQUEST_HEADERS:
179 str = apr_table_get(r->headers_in, provider->value);
181 case RESPONSE_HEADERS:
182 str = apr_table_get(r->headers_out, provider->value);
185 str = apr_table_get(r->subprocess_env, provider->value);
188 str = r->content_type;
195 /* treat nulls so we don't have to check every strcmp individually
196 * Not sure if there's anything better to do with them
199 if (provider->match_type == DEFINED && provider->match.string) {
203 /* we can't check for NULL in provider as that kills integer 0
204 * so we have to test each string/regexp case in the switch
207 switch (provider->match_type) {
209 if (strcasecmp(str, provider->match.string)) {
213 case STRING_CONTAINS:
214 str1 = apr_pstrdup(r->pool, str);
215 ap_str_tolower(str1);
216 if (!strstr(str1, provider->match.string)) {
221 if (ap_regexec(provider->match.regex, str, 0, NULL, 0)
227 if (atoi(str) != provider->match.number) {
231 /* Integer comparisons should be [var] OP [match]
232 * We need to set match = 0 if the condition fails
235 if (atoi(str) >= provider->match.number) {
240 if (atoi(str) > provider->match.number) {
245 if (atoi(str) <= provider->match.number) {
250 if (atoi(str) < provider->match.number) {
254 case DEFINED: /* we already handled this:-) */
259 if (match != provider->not) {
260 /* condition matches this provider */
265 * This is a quick hack and almost certainly buggy.
266 * The idea is that by putting this in mod_filter, we relieve
267 * filter implementations of the burden of fixing up HTTP headers
268 * for cases that are routinely affected by filters.
270 * Default is ALWAYS to do nothing, so as not to tread on the
271 * toes of filters which want to do it themselves.
274 proto_flags = provider->frec->proto_flags;
276 /* some specific things can't happen in a proxy */
278 if (proto_flags & AP_FILTER_PROTO_NO_PROXY) {
279 /* can't use this provider; try next */
283 if (proto_flags & AP_FILTER_PROTO_TRANSFORM) {
284 str = apr_table_get(r->headers_out, "Cache-Control");
286 str1 = apr_pstrdup(r->pool, str);
287 ap_str_tolower(str1);
288 if (strstr(str1, "no-transform")) {
289 /* can't use this provider; try next */
293 apr_table_addn(r->headers_out, "Warning",
294 apr_psprintf(r->pool,
295 "214 %s Transformation applied",
300 /* things that are invalidated if the filter transforms content */
301 if (proto_flags & AP_FILTER_PROTO_CHANGE) {
302 apr_table_unset(r->headers_out, "Content-MD5");
303 apr_table_unset(r->headers_out, "ETag");
304 if (proto_flags & AP_FILTER_PROTO_CHANGE_LENGTH) {
305 apr_table_unset(r->headers_out, "Content-Length");
309 /* no-cache is for a filter that has different effect per-hit */
310 if (proto_flags & AP_FILTER_PROTO_NO_CACHE) {
311 apr_table_unset(r->headers_out, "Last-Modified");
312 apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
315 if (proto_flags & AP_FILTER_PROTO_NO_BYTERANGE) {
316 apr_table_unset(r->headers_out, "Accept-Ranges");
318 else if (rctx && rctx->range) {
319 /* restore range header we saved earlier */
320 apr_table_setn(r->headers_in, "Range", rctx->range);
324 for (pctx = ctx->init_ctx; pctx; pctx = pctx->next) {
325 if (pctx->provider == provider) {
326 ctx->fctx = pctx->ctx ;
329 ctx->func = provider->frec->filter_func.out_func;
334 /* No provider matched */
338 static apr_status_t filter_harness(ap_filter_t *f, apr_bucket_brigade *bb)
341 const char *cachecontrol;
343 harness_ctx *ctx = f->ctx;
344 ap_filter_rec_t *filter = f->frec;
346 if (f->r->status != 200) {
347 ap_remove_output_filter(f);
348 return ap_pass_brigade(f->next, bb);
351 filter_trace(f->c, filter->debug, f->frec->name, bb);
353 /* look up a handler function if we haven't already set it */
356 if (f->r->proxyreq) {
357 if (filter->proto_flags & AP_FILTER_PROTO_NO_PROXY) {
358 ap_remove_output_filter(f);
359 return ap_pass_brigade(f->next, bb);
362 if (filter->proto_flags & AP_FILTER_PROTO_TRANSFORM) {
363 cachecontrol = apr_table_get(f->r->headers_out,
366 str = apr_pstrdup(f->r->pool, cachecontrol);
368 if (strstr(str, "no-transform")) {
369 ap_remove_output_filter(f);
370 return ap_pass_brigade(f->next, bb);
376 if (!filter_lookup(f, filter)) {
377 ap_remove_output_filter(f);
378 return ap_pass_brigade(f->next, bb);
382 /* call the content filter with its own context, then restore our
386 ret = ctx->func(f, bb);
394 static const char *filter_protocol(cmd_parms *cmd, void *CFG, const char *fname,
395 const char *pname, const char *proto)
397 static const char *sep = ";, \t";
400 unsigned int flags = 0;
401 mod_filter_cfg *cfg = CFG;
402 ap_filter_provider_t *provider = NULL;
403 ap_filter_rec_t *filter = apr_hash_get(cfg->live_filters, fname,
404 APR_HASH_KEY_STRING);
407 return "FilterProtocol: No such filter";
410 /* Fixup the args: it's really pname that's optional */
417 for (provider = filter->providers; provider; provider = provider->next){
418 if (!strcasecmp(provider->frec->name, pname)) {
423 return "FilterProtocol: No such provider for this filter";
427 /* Now set flags from our args */
428 for (arg = apr_strtok(apr_pstrdup(cmd->pool, proto), sep, &tok);
429 arg; arg = apr_strtok(NULL, sep, &tok)) {
431 if (!strcasecmp(arg, "change=yes")) {
432 flags |= AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH;
434 else if (!strcasecmp(arg, "change=1:1")) {
435 flags |= AP_FILTER_PROTO_CHANGE;
437 else if (!strcasecmp(arg, "byteranges=no")) {
438 flags |= AP_FILTER_PROTO_NO_BYTERANGE;
440 else if (!strcasecmp(arg, "proxy=no")) {
441 flags |= AP_FILTER_PROTO_NO_PROXY;
443 else if (!strcasecmp(arg, "proxy=transform")) {
444 flags |= AP_FILTER_PROTO_TRANSFORM;
446 else if (!strcasecmp(arg, "cache=no")) {
447 flags |= AP_FILTER_PROTO_NO_CACHE;
452 provider->frec->proto_flags = flags;
455 filter->proto_flags = flags;
462 static const char *filter_declare(cmd_parms *cmd, void *CFG, const char *fname,
465 mod_filter_cfg *cfg = (mod_filter_cfg *)CFG;
466 ap_filter_rec_t *filter;
468 filter = apr_pcalloc(cmd->pool, sizeof(ap_filter_rec_t));
469 apr_hash_set(cfg->live_filters, fname, APR_HASH_KEY_STRING, filter);
471 filter->name = fname;
472 filter->filter_init_func = filter_init;
473 filter->filter_func.out_func = filter_harness;
474 filter->ftype = AP_FTYPE_RESOURCE;
478 if (!strcasecmp(place, "CONTENT_SET")) {
479 filter->ftype = AP_FTYPE_CONTENT_SET;
481 else if (!strcasecmp(place, "PROTOCOL")) {
482 filter->ftype = AP_FTYPE_PROTOCOL;
484 else if (!strcasecmp(place, "CONNECTION")) {
485 filter->ftype = AP_FTYPE_CONNECTION;
487 else if (!strcasecmp(place, "NETWORK")) {
488 filter->ftype = AP_FTYPE_NETWORK;
495 static const char *filter_provider(cmd_parms *cmd, void *CFG, const char *args)
497 mod_filter_cfg *cfg = CFG;
499 ap_filter_provider_t *provider;
504 ap_filter_rec_t* frec;
505 ap_filter_rec_t* provider_frec;
507 /* insist on exactly four arguments */
508 const char *fname = ap_getword_conf(cmd->pool, &args) ;
509 const char *pname = ap_getword_conf(cmd->pool, &args) ;
510 const char *condition = ap_getword_conf(cmd->pool, &args) ;
511 const char *match = ap_getword_conf(cmd->pool, &args) ;
512 eq = ap_getword_conf(cmd->pool, &args) ;
513 if ( !*fname || !*pname || !*match || !*condition || *eq ) {
514 return "usage: FilterProvider filter provider condition match" ;
517 /* fname has been declared with DeclareFilter, so we can look it up */
518 frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
520 /* or if provider is mod_filter itself, we can also look it up */
522 c = filter_declare(cmd, CFG, fname, NULL);
526 frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
530 return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
533 /* if provider has been registered, we can look it up */
534 provider_frec = ap_get_output_filter_handle(pname);
535 if (!provider_frec) {
536 provider_frec = apr_hash_get(cfg->live_filters, pname,
537 APR_HASH_KEY_STRING);
539 if (!provider_frec) {
540 return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname);
543 provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t));
555 provider->match_type = INT_LE;
559 provider->match_type = INT_LT;
561 provider->match.number = atoi(match);
565 provider->match_type = INT_GE;
569 provider->match_type = INT_GT;
571 provider->match.number = atoi(match);
574 provider->match_type = INT_EQ;
575 provider->match.number = atoi(match);
578 provider->match_type = REGEX_MATCH;
579 rxend = ap_strchr_c(match, '/');
581 return "Bad regexp syntax";
583 flags = AP_REG_NOSUB; /* we're not mod_rewrite:-) */
584 for (c = rxend+1; *c; ++c) {
586 case 'i': flags |= AP_REG_ICASE; break;
589 provider->match.regex = ap_pregcomp(cmd->pool,
590 apr_pstrndup(cmd->pool,
594 if (provider->match.regex == NULL) {
599 provider->match_type = DEFINED;
600 provider->match.number = -1;
603 provider->match_type = STRING_CONTAINS;
604 str = apr_pstrdup(cmd->pool, match);
606 provider->match.string = str;
609 provider->match_type = STRING_MATCH;
610 provider->match.string = apr_pstrdup(cmd->pool, match-1);
613 provider->frec = provider_frec;
614 provider->next = frec->providers;
615 frec->providers = provider;
617 /* determine what a filter will dispatch this provider on */
618 eq = ap_strchr_c(condition, '=');
620 str = apr_pstrdup(cmd->pool, eq+1);
621 if (!strncasecmp(condition, "env=", 4)) {
622 provider->dispatch = SUBPROCESS_ENV;
624 else if (!strncasecmp(condition, "req=", 4)) {
625 provider->dispatch = REQUEST_HEADERS;
627 else if (!strncasecmp(condition, "resp=", 5)) {
628 provider->dispatch = RESPONSE_HEADERS;
631 return "FilterProvider: unrecognized dispatch table";
635 if (!strcasecmp(condition, "handler")) {
636 provider->dispatch = HANDLER;
639 provider->dispatch = RESPONSE_HEADERS;
641 str = apr_pstrdup(cmd->pool, condition);
645 if ( (provider->dispatch == RESPONSE_HEADERS)
646 && !strcasecmp(str, "content-type")) {
647 provider->dispatch = CONTENT_TYPE;
649 provider->value = str;
654 static const char *filter_chain(cmd_parms *cmd, void *CFG, const char *arg)
658 mod_filter_cfg *cfg = CFG;
661 case '+': /* add to end of chain */
662 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
665 for (q = cfg->chain; q->next; q = q->next);
673 case '@': /* add to start of chain */
674 p = apr_palloc(cmd->pool, sizeof(mod_filter_chain));
676 p->next = cfg->chain;
680 case '-': /* remove from chain */
682 if (strcasecmp(cfg->chain->fname, arg+1)) {
683 for (p = cfg->chain; p->next; p = p->next) {
684 if (!strcasecmp(p->next->fname, arg+1)) {
685 p->next = p->next->next;
690 cfg->chain = cfg->chain->next;
695 case '!': /* Empty the chain */
696 /** IG: Add a NULL provider to the beginning so that
697 * we can ensure that we'll empty everything before
698 * this when doing config merges later */
699 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
704 case '=': /* initialise chain with this arg */
705 /** IG: Prepend a NULL provider to the beginning as above */
706 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
708 p->next = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
709 p->next->fname = arg+1;
713 default: /* add to end */
714 p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
717 for (q = cfg->chain; q->next; q = q->next);
729 static const char *filter_debug(cmd_parms *cmd, void *CFG, const char *fname,
732 mod_filter_cfg *cfg = CFG;
733 ap_filter_rec_t *frec = apr_hash_get(cfg->live_filters, fname,
734 APR_HASH_KEY_STRING);
736 return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
738 frec->debug = atoi(level);
743 static void filter_insert(request_rec *r)
746 ap_filter_rec_t *filter;
747 mod_filter_cfg *cfg = ap_get_module_config(r->per_dir_config,
751 mod_filter_ctx *ctx = apr_pcalloc(r->pool, sizeof(mod_filter_ctx));
752 ap_set_module_config(r->request_config, &filter_module, ctx);
755 /** IG: Now that we've merged to the final config, go one last time
756 * through the chain, and prune out the NULL filters */
758 for (p = cfg->chain; p; p = p->next) {
759 if (p->fname == NULL)
760 cfg->chain = p->next;
763 for (p = cfg->chain; p; p = p->next) {
764 filter = apr_hash_get(cfg->live_filters, p->fname, APR_HASH_KEY_STRING);
765 if (filter == NULL) {
766 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
767 "Unknown filter %s not added", p->fname);
770 ap_add_output_filter_handle(filter, NULL, r, r->connection);
772 if (ranges && (filter->proto_flags
773 & (AP_FILTER_PROTO_NO_BYTERANGE
774 | AP_FILTER_PROTO_CHANGE_LENGTH))) {
775 ctx->range = apr_table_get(r->headers_in, "Range");
776 apr_table_unset(r->headers_in, "Range");
785 static void filter_hooks(apr_pool_t *pool)
787 ap_hook_insert_filter(filter_insert, NULL, NULL, APR_HOOK_MIDDLE);
790 static void *filter_config(apr_pool_t *pool, char *x)
792 mod_filter_cfg *cfg = apr_palloc(pool, sizeof(mod_filter_cfg));
793 cfg->live_filters = apr_hash_make(pool);
798 static void *filter_merge(apr_pool_t *pool, void *BASE, void *ADD)
800 mod_filter_cfg *base = BASE;
801 mod_filter_cfg *add = ADD;
802 mod_filter_chain *savelink = 0;
803 mod_filter_chain *newlink;
805 mod_filter_cfg *conf = apr_palloc(pool, sizeof(mod_filter_cfg));
807 conf->live_filters = apr_hash_overlay(pool, add->live_filters,
809 if (base->chain && add->chain) {
810 for (p = base->chain; p; p = p->next) {
811 newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
812 if (newlink->fname == NULL) {
813 conf->chain = savelink = newlink;
816 savelink->next = newlink;
820 conf->chain = savelink = newlink;
824 for (p = add->chain; p; p = p->next) {
825 newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
826 /** Filter out merged chain resets */
827 if (newlink->fname == NULL) {
828 conf->chain = savelink = newlink;
831 savelink->next = newlink;
835 conf->chain = savelink = newlink;
839 else if (add->chain) {
840 conf->chain = add->chain;
843 conf->chain = base->chain;
849 static const command_rec filter_cmds[] = {
850 AP_INIT_TAKE12("FilterDeclare", filter_declare, NULL, OR_OPTIONS,
851 "filter-name [, filter-type]"),
852 /** we don't have a TAKE4, so we have to use RAW_ARGS */
853 AP_INIT_RAW_ARGS("FilterProvider", filter_provider, NULL, OR_OPTIONS,
854 "filter-name, provider-name, dispatch--criterion, dispatch-match"),
855 AP_INIT_ITERATE("FilterChain", filter_chain, NULL, OR_OPTIONS,
856 "list of filter names with optional [+-=!@]"),
857 AP_INIT_TAKE2("FilterTrace", filter_debug, NULL, RSRC_CONF | ACCESS_CONF,
860 AP_INIT_TAKE23("FilterProtocol", filter_protocol, NULL, OR_OPTIONS,
861 "filter-name [provider-name] protocol-args"),
866 module AP_MODULE_DECLARE_DATA filter_module = {
867 STANDARD20_MODULE_STUFF,