summaryrefslogtreecommitdiffstats
path: root/modules/filters/mod_filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'modules/filters/mod_filter.c')
-rw-r--r--modules/filters/mod_filter.c767
1 files changed, 767 insertions, 0 deletions
diff --git a/modules/filters/mod_filter.c b/modules/filters/mod_filter.c
new file mode 100644
index 0000000..5b5ecf6
--- /dev/null
+++ b/modules/filters/mod_filter.c
@@ -0,0 +1,767 @@
+/* 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
+};