/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define APR_WANT_STRFUNC #include "apr_want.h" #include "apr_lib.h" #include "apr_strings.h" #include "apr_hash.h" #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_log.h" #include "util_filter.h" #include "ap_expr.h" module AP_MODULE_DECLARE_DATA filter_module; /** * @brief is a filter provider, as defined and implemented by mod_filter. * * The struct is a linked list, with dispatch criteria * defined for each filter. The provider implementation itself is a * (2.0-compatible) ap_filter_rec_t* frec. */ struct ap_filter_provider_t { ap_expr_info_t *expr; const char **types; /** The filter that implements this provider */ ap_filter_rec_t *frec; /** The next provider in the list */ ap_filter_provider_t *next; }; /** we need provider_ctx to save ctx values set by providers in filter_init */ typedef struct provider_ctx provider_ctx; struct provider_ctx { ap_filter_provider_t *provider; void *ctx; provider_ctx *next; }; typedef struct { ap_out_filter_func func; void *fctx; provider_ctx *init_ctx; } harness_ctx; typedef struct mod_filter_chain { const char *fname; struct mod_filter_chain *next; } mod_filter_chain; typedef struct { apr_hash_t *live_filters; mod_filter_chain *chain; } mod_filter_cfg; typedef struct { const char* range ; } mod_filter_ctx ; static void filter_trace(conn_rec *c, int debug, const char *fname, apr_bucket_brigade *bb) { apr_bucket *b; switch (debug) { case 0: /* normal, operational use */ return; case 1: /* mod_diagnostics level */ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01375) "%s", fname); for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01376) "%s: type: %s, length: %" APR_SIZE_T_FMT, fname, b->type->name ? b->type->name : "(unknown)", b->length); } break; } } static int filter_init(ap_filter_t *f) { ap_filter_provider_t *p; provider_ctx *pctx; int err; ap_filter_rec_t *filter = f->frec; harness_ctx *fctx = apr_pcalloc(f->r->pool, sizeof(harness_ctx)); for (p = filter->providers; p; p = p->next) { if (p->frec->filter_init_func == filter_init) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(01377) "Chaining of FilterProviders not supported"); return HTTP_INTERNAL_SERVER_ERROR; } else if (p->frec->filter_init_func) { f->ctx = NULL; if ((err = p->frec->filter_init_func(f)) != OK) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(01378) "filter_init for %s failed", p->frec->name); return err; /* if anyone errors out here, so do we */ } if (f->ctx != NULL) { /* the filter init function set a ctx - we need to record it */ pctx = apr_pcalloc(f->r->pool, sizeof(provider_ctx)); pctx->provider = p; pctx->ctx = f->ctx; pctx->next = fctx->init_ctx; fctx->init_ctx = pctx; } } } f->ctx = fctx; return OK; } static int filter_lookup(ap_filter_t *f, ap_filter_rec_t *filter) { ap_filter_provider_t *provider; int match = 0; const char *err = NULL; request_rec *r = f->r; harness_ctx *ctx = f->ctx; provider_ctx *pctx; #ifndef NO_PROTOCOL unsigned int proto_flags; mod_filter_ctx *rctx = ap_get_module_config(r->request_config, &filter_module); #endif /* Check registered providers in order */ for (provider = filter->providers; provider; provider = provider->next) { if (provider->expr) { match = ap_expr_exec(r, provider->expr, &err); if (err) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01379) "Error evaluating filter dispatch condition: %s", err); match = 0; } ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "Expression condition for '%s' %s", provider->frec->name, match ? "matched" : "did not match"); } else if (r->content_type) { const char **type = provider->types; size_t len = strcspn(r->content_type, "; \t"); AP_DEBUG_ASSERT(type != NULL); ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "Content-Type '%s' ...", r->content_type); while (*type) { /* Handle 'content-type;charset=...' correctly */ if (strncmp(*type, r->content_type, len) == 0 && (*type)[len] == '\0') { ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "... matched '%s'", *type); match = 1; break; } else { ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "... did not match '%s'", *type); } type++; } ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "Content-Type condition for '%s' %s", provider->frec->name, match ? "matched" : "did not match"); } else { ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "Content-Type condition for '%s' did not match: " "no Content-Type", provider->frec->name); } if (match) { /* condition matches this provider */ #ifndef NO_PROTOCOL /* check protocol * * FIXME: * This is a quick hack and almost certainly buggy. * The idea is that by putting this in mod_filter, we relieve * filter implementations of the burden of fixing up HTTP headers * for cases that are routinely affected by filters. * * Default is ALWAYS to do nothing, so as not to tread on the * toes of filters which want to do it themselves. * */ proto_flags = provider->frec->proto_flags; /* some specific things can't happen in a proxy */ if (r->proxyreq) { if (proto_flags & AP_FILTER_PROTO_NO_PROXY) { /* can't use this provider; try next */ continue; } if (proto_flags & AP_FILTER_PROTO_TRANSFORM) { const char *str = apr_table_get(r->headers_out, "Cache-Control"); if (str) { if (ap_strcasestr(str, "no-transform")) { /* can't use this provider; try next */ continue; } } apr_table_addn(r->headers_out, "Warning", apr_psprintf(r->pool, "214 %s Transformation applied", r->hostname)); } } /* things that are invalidated if the filter transforms content */ if (proto_flags & AP_FILTER_PROTO_CHANGE) { apr_table_unset(r->headers_out, "Content-MD5"); apr_table_unset(r->headers_out, "ETag"); if (proto_flags & AP_FILTER_PROTO_CHANGE_LENGTH) { apr_table_unset(r->headers_out, "Content-Length"); } } /* no-cache is for a filter that has different effect per-hit */ if (proto_flags & AP_FILTER_PROTO_NO_CACHE) { apr_table_unset(r->headers_out, "Last-Modified"); apr_table_addn(r->headers_out, "Cache-Control", "no-cache"); } if (proto_flags & AP_FILTER_PROTO_NO_BYTERANGE) { apr_table_setn(r->headers_out, "Accept-Ranges", "none"); } else if (rctx && rctx->range) { /* restore range header we saved earlier */ apr_table_setn(r->headers_in, "Range", rctx->range); rctx->range = NULL; } #endif for (pctx = ctx->init_ctx; pctx; pctx = pctx->next) { if (pctx->provider == provider) { ctx->fctx = pctx->ctx ; } } ctx->func = provider->frec->filter_func.out_func; return 1; } } /* No provider matched */ return 0; } static apr_status_t filter_harness(ap_filter_t *f, apr_bucket_brigade *bb) { apr_status_t ret; #ifndef NO_PROTOCOL const char *cachecontrol; #endif harness_ctx *ctx = f->ctx; ap_filter_rec_t *filter = f->frec; if (f->r->status != 200 && !apr_table_get(f->r->subprocess_env, "filter-errordocs")) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } filter_trace(f->c, filter->debug, f->frec->name, bb); /* look up a handler function if we haven't already set it */ if (!ctx->func) { #ifndef NO_PROTOCOL if (f->r->proxyreq) { if (filter->proto_flags & AP_FILTER_PROTO_NO_PROXY) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } if (filter->proto_flags & AP_FILTER_PROTO_TRANSFORM) { cachecontrol = apr_table_get(f->r->headers_out, "Cache-Control"); if (cachecontrol) { if (ap_strcasestr(cachecontrol, "no-transform")) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } } } } #endif if (!filter_lookup(f, filter)) { ap_remove_output_filter(f); return ap_pass_brigade(f->next, bb); } AP_DEBUG_ASSERT(ctx->func != NULL); } /* call the content filter with its own context, then restore our * context */ f->ctx = ctx->fctx; ret = ctx->func(f, bb); ctx->fctx = f->ctx; f->ctx = ctx; return ret; } #ifndef NO_PROTOCOL static const char *filter_protocol(cmd_parms *cmd, void *CFG, const char *fname, const char *pname, const char *proto) { static const char *sep = ";, \t"; char *arg; char *tok = 0; unsigned int flags = 0; mod_filter_cfg *cfg = CFG; ap_filter_provider_t *provider = NULL; ap_filter_rec_t *filter = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING); if (!filter) { return "FilterProtocol: No such filter"; } /* Fixup the args: it's really pname that's optional */ if (proto == NULL) { proto = pname; pname = NULL; } else { /* Find provider */ for (provider = filter->providers; provider; provider = provider->next) { if (!strcasecmp(provider->frec->name, pname)) { break; } } if (!provider) { return "FilterProtocol: No such provider for this filter"; } } /* Now set flags from our args */ for (arg = apr_strtok(apr_pstrdup(cmd->temp_pool, proto), sep, &tok); arg; arg = apr_strtok(NULL, sep, &tok)) { if (!strcasecmp(arg, "change=yes")) { flags |= AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH; } if (!strcasecmp(arg, "change=no")) { flags &= ~(AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH); } else if (!strcasecmp(arg, "change=1:1")) { flags |= AP_FILTER_PROTO_CHANGE; } else if (!strcasecmp(arg, "byteranges=no")) { flags |= AP_FILTER_PROTO_NO_BYTERANGE; } else if (!strcasecmp(arg, "proxy=no")) { flags |= AP_FILTER_PROTO_NO_PROXY; } else if (!strcasecmp(arg, "proxy=transform")) { flags |= AP_FILTER_PROTO_TRANSFORM; } else if (!strcasecmp(arg, "cache=no")) { flags |= AP_FILTER_PROTO_NO_CACHE; } } if (pname) { provider->frec->proto_flags = flags; } else { filter->proto_flags = flags; } return NULL; } #endif static const char *filter_declare(cmd_parms *cmd, void *CFG, const char *fname, const char *place) { mod_filter_cfg *cfg = (mod_filter_cfg *)CFG; ap_filter_rec_t *filter; filter = apr_pcalloc(cmd->pool, sizeof(ap_filter_rec_t)); apr_hash_set(cfg->live_filters, fname, APR_HASH_KEY_STRING, filter); filter->name = fname; filter->filter_init_func = filter_init; filter->filter_func.out_func = filter_harness; filter->ftype = AP_FTYPE_RESOURCE; filter->next = NULL; if (place) { if (!strcasecmp(place, "CONTENT_SET")) { filter->ftype = AP_FTYPE_CONTENT_SET; } else if (!strcasecmp(place, "PROTOCOL")) { filter->ftype = AP_FTYPE_PROTOCOL; } else if (!strcasecmp(place, "CONNECTION")) { filter->ftype = AP_FTYPE_CONNECTION; } else if (!strcasecmp(place, "NETWORK")) { filter->ftype = AP_FTYPE_NETWORK; } } return NULL; } static const char *add_filter(cmd_parms *cmd, void *CFG, const char *fname, const char *pname, const char *expr, const char **types) { mod_filter_cfg *cfg = CFG; ap_filter_provider_t *provider; const char *c; ap_filter_rec_t* frec; ap_filter_rec_t* provider_frec; ap_expr_info_t *node; const char *err = NULL; /* if provider has been registered, we can look it up */ provider_frec = ap_get_output_filter_handle(pname); if (!provider_frec) { return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname); } /* fname has been declared with DeclareFilter, so we can look it up */ frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING); /* or if provider is mod_filter itself, we can also look it up */ if (!frec) { c = filter_declare(cmd, CFG, fname, NULL); if ( c ) { return c; } frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING); frec->ftype = provider_frec->ftype; } if (!frec) { return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname); } provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t)); if (expr) { node = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL); if (err) { return apr_pstrcat(cmd->pool, "Error parsing FilterProvider expression:", err, NULL); } provider->expr = node; provider->types = NULL; } else { provider->types = types; provider->expr = NULL; } provider->frec = provider_frec; provider->next = frec->providers; frec->providers = provider; return NULL; } static const char *filter_provider(cmd_parms *cmd, void *CFG, const char *fname, const char *pname, const char *expr) { return add_filter(cmd, CFG, fname, pname, expr, NULL); } static const char *filter_chain(cmd_parms *cmd, void *CFG, const char *arg) { mod_filter_chain *p; mod_filter_chain *q; mod_filter_cfg *cfg = CFG; switch (arg[0]) { case '+': /* add to end of chain */ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)); p->fname = arg+1; if (cfg->chain) { for (q = cfg->chain; q->next; q = q->next); q->next = p; } else { cfg->chain = p; } break; case '@': /* add to start of chain */ p = apr_palloc(cmd->pool, sizeof(mod_filter_chain)); p->fname = arg+1; p->next = cfg->chain; cfg->chain = p; break; case '-': /* remove from chain */ if (cfg->chain) { if (strcasecmp(cfg->chain->fname, arg+1)) { for (p = cfg->chain; p->next; p = p->next) { if (!strcasecmp(p->next->fname, arg+1)) { p->next = p->next->next; } } } else { cfg->chain = cfg->chain->next; } } break; case '!': /* Empty the chain */ /** IG: Add a NULL provider to the beginning so that * we can ensure that we'll empty everything before * this when doing config merges later */ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)); p->fname = NULL; cfg->chain = p; break; case '=': /* initialise chain with this arg */ /** IG: Prepend a NULL provider to the beginning as above */ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)); p->fname = NULL; p->next = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)); p->next->fname = arg+1; cfg->chain = p; break; default: /* add to end */ p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain)); p->fname = arg; if (cfg->chain) { for (q = cfg->chain; q->next; q = q->next); q->next = p; } else { cfg->chain = p; } break; } return NULL; } static const char *filter_bytype1(cmd_parms *cmd, void *CFG, const char *pname, const char **types) { const char *rv; const char *fname; int seen_name = 0; mod_filter_cfg *cfg = CFG; /* construct fname from name */ fname = apr_pstrcat(cmd->pool, "BYTYPE:", pname, NULL); /* check whether this is already registered, in which case * it's already in the filter chain */ if (apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING)) { seen_name = 1; } rv = add_filter(cmd, CFG, fname, pname, NULL, types); /* If it's the first time through, add to filterchain */ if (rv == NULL && !seen_name) { rv = filter_chain(cmd, CFG, fname); } return rv; } static const char *filter_bytype(cmd_parms *cmd, void *CFG, int argc, char *const argv[]) { /* back compatibility, need to parse multiple components in filter name */ char *pname; char *strtok_state = NULL; char *name; const char **types; const char *rv = NULL; if (argc < 2) return "AddOutputFilterByType requires at least two arguments"; name = apr_pstrdup(cmd->temp_pool, argv[0]); types = apr_palloc(cmd->pool, argc * sizeof(char *)); memcpy(types, &argv[1], (argc - 1) * sizeof(char *)); types[argc-1] = NULL; for (pname = apr_strtok(name, ";", &strtok_state); pname != NULL && rv == NULL; pname = apr_strtok(NULL, ";", &strtok_state)) { rv = filter_bytype1(cmd, CFG, pname, types); } return rv; } static const char *filter_debug(cmd_parms *cmd, void *CFG, const char *fname, const char *level) { mod_filter_cfg *cfg = CFG; ap_filter_rec_t *frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING); if (!frec) { return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname); } frec->debug = atoi(level); return NULL; } static void filter_insert(request_rec *r) { mod_filter_chain *p; ap_filter_rec_t *filter; mod_filter_cfg *cfg = ap_get_module_config(r->per_dir_config, &filter_module); #ifndef NO_PROTOCOL int ranges = 1; mod_filter_ctx *ctx = apr_pcalloc(r->pool, sizeof(mod_filter_ctx)); ap_set_module_config(r->request_config, &filter_module, ctx); #endif /** IG: Now that we've merged to the final config, go one last time * through the chain, and prune out the NULL filters */ for (p = cfg->chain; p; p = p->next) { if (p->fname == NULL) cfg->chain = p->next; } for (p = cfg->chain; p; p = p->next) { filter = apr_hash_get(cfg->live_filters, p->fname, APR_HASH_KEY_STRING); if (filter == NULL) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01380) "Unknown filter %s not added", p->fname); continue; } ap_add_output_filter_handle(filter, NULL, r, r->connection); #ifndef NO_PROTOCOL if (ranges && (filter->proto_flags & (AP_FILTER_PROTO_NO_BYTERANGE | AP_FILTER_PROTO_CHANGE_LENGTH))) { ctx->range = apr_table_get(r->headers_in, "Range"); apr_table_unset(r->headers_in, "Range"); ranges = 0; } #endif } } static void filter_hooks(apr_pool_t *pool) { ap_hook_insert_filter(filter_insert, NULL, NULL, APR_HOOK_MIDDLE); } static void *filter_config(apr_pool_t *pool, char *x) { mod_filter_cfg *cfg = apr_palloc(pool, sizeof(mod_filter_cfg)); cfg->live_filters = apr_hash_make(pool); cfg->chain = NULL; return cfg; } static void *filter_merge(apr_pool_t *pool, void *BASE, void *ADD) { mod_filter_cfg *base = BASE; mod_filter_cfg *add = ADD; mod_filter_chain *savelink = 0; mod_filter_chain *newlink; mod_filter_chain *p; mod_filter_cfg *conf = apr_palloc(pool, sizeof(mod_filter_cfg)); conf->live_filters = apr_hash_overlay(pool, add->live_filters, base->live_filters); if (base->chain && add->chain) { for (p = base->chain; p; p = p->next) { newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain)); if (newlink->fname == NULL) { conf->chain = savelink = newlink; } else if (savelink) { savelink->next = newlink; savelink = newlink; } else { conf->chain = savelink = newlink; } } for (p = add->chain; p; p = p->next) { newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain)); /** Filter out merged chain resets */ if (newlink->fname == NULL) { conf->chain = savelink = newlink; } else if (savelink) { savelink->next = newlink; savelink = newlink; } else { conf->chain = savelink = newlink; } } } else if (add->chain) { conf->chain = add->chain; } else { conf->chain = base->chain; } return conf; } static const command_rec filter_cmds[] = { AP_INIT_TAKE12("FilterDeclare", filter_declare, NULL, OR_OPTIONS, "filter-name [filter-type]"), 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, "filter-name debug-level"), AP_INIT_TAKE_ARGV("AddOutputFilterByType", filter_bytype, NULL, OR_FILEINFO, "output filter name followed by one or more content-types"), #ifndef NO_PROTOCOL AP_INIT_TAKE23("FilterProtocol", filter_protocol, NULL, OR_OPTIONS, "filter-name [provider-name] protocol-args"), #endif { NULL } }; AP_DECLARE_MODULE(filter) = { STANDARD20_MODULE_STUFF, filter_config, filter_merge, NULL, NULL, filter_cmds, filter_hooks };