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
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17#define APR_WANT_STRFUNC
18#include "apr_want.h"
19#include "apr_lib.h"
20#include "apr_strings.h"
21#include "apr_hash.h"
22#include "httpd.h"
23#include "http_config.h"
24#include "http_request.h"
25#include "http_log.h"
26#include "util_filter.h"
27#include "ap_expr.h"
28
29module AP_MODULE_DECLARE_DATA filter_module;
30
31/**
32 * @brief is a filter provider, as defined and implemented by mod_filter.
33 *
34 * The struct is a linked list, with dispatch criteria
35 * defined for each filter.  The provider implementation itself is a
36 * (2.0-compatible) ap_filter_rec_t* frec.
37 */
38struct ap_filter_provider_t {
39    ap_expr_info_t *expr;
40    const char **types;
41
42    /** The filter that implements this provider */
43    ap_filter_rec_t *frec;
44
45    /** The next provider in the list */
46    ap_filter_provider_t *next;
47};
48
49/** we need provider_ctx to save ctx values set by providers in filter_init */
50typedef struct provider_ctx provider_ctx;
51struct provider_ctx {
52    ap_filter_provider_t *provider;
53    void *ctx;
54    provider_ctx *next;
55};
56typedef struct {
57    ap_out_filter_func func;
58    void *fctx;
59    provider_ctx *init_ctx;
60} harness_ctx;
61
62typedef struct mod_filter_chain {
63    const char *fname;
64    struct mod_filter_chain *next;
65} mod_filter_chain;
66
67typedef struct {
68    apr_hash_t *live_filters;
69    mod_filter_chain *chain;
70} mod_filter_cfg;
71
72typedef struct {
73    const char* range ;
74} mod_filter_ctx ;
75
76
77static void filter_trace(conn_rec *c, int debug, const char *fname,
78                         apr_bucket_brigade *bb)
79{
80    apr_bucket *b;
81
82    switch (debug) {
83    case 0:        /* normal, operational use */
84        return;
85    case 1:        /* mod_diagnostics level */
86        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01375) "%s", fname);
87        for (b = APR_BRIGADE_FIRST(bb);
88             b != APR_BRIGADE_SENTINEL(bb);
89             b = APR_BUCKET_NEXT(b)) {
90
91            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01376)
92                          "%s: type: %s, length: %" APR_SIZE_T_FMT,
93                          fname, b->type->name ? b->type->name : "(unknown)",
94                          b->length);
95        }
96        break;
97    }
98}
99
100static int filter_init(ap_filter_t *f)
101{
102    ap_filter_provider_t *p;
103    provider_ctx *pctx;
104    int err;
105    ap_filter_rec_t *filter = f->frec;
106
107    harness_ctx *fctx = apr_pcalloc(f->r->pool, sizeof(harness_ctx));
108    for (p = filter->providers; p; p = p->next) {
109        if (p->frec->filter_init_func == filter_init) {
110            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(01377)
111                          "Chaining of FilterProviders not supported");
112            return HTTP_INTERNAL_SERVER_ERROR;
113        }
114        else if (p->frec->filter_init_func) {
115            f->ctx = NULL;
116            if ((err = p->frec->filter_init_func(f)) != OK) {
117                ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, f->c, APLOGNO(01378)
118                              "filter_init for %s failed", p->frec->name);
119                return err;   /* if anyone errors out here, so do we */
120            }
121            if (f->ctx != NULL) {
122                /* the filter init function set a ctx - we need to record it */
123                pctx = apr_pcalloc(f->r->pool, sizeof(provider_ctx));
124                pctx->provider = p;
125                pctx->ctx = f->ctx;
126                pctx->next = fctx->init_ctx;
127                fctx->init_ctx = pctx;
128            }
129        }
130    }
131    f->ctx = fctx;
132    return OK;
133}
134
135static int filter_lookup(ap_filter_t *f, ap_filter_rec_t *filter)
136{
137    ap_filter_provider_t *provider;
138    int match = 0;
139    const char *err = NULL;
140    request_rec *r = f->r;
141    harness_ctx *ctx = f->ctx;
142    provider_ctx *pctx;
143#ifndef NO_PROTOCOL
144    unsigned int proto_flags;
145    mod_filter_ctx *rctx = ap_get_module_config(r->request_config,
146                                                &filter_module);
147#endif
148
149    /* Check registered providers in order */
150    for (provider = filter->providers; provider; provider = provider->next) {
151        if (provider->expr) {
152            match = ap_expr_exec(r, provider->expr, &err);
153            if (err) {
154                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01379)
155                              "Error evaluating filter dispatch condition: %s",
156                              err);
157                match = 0;
158            }
159            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
160                          "Expression condition for '%s' %s",
161                          provider->frec->name,
162                          match ? "matched" : "did not match");
163        }
164        else if (r->content_type) {
165            const char **type = provider->types;
166            size_t len = strcspn(r->content_type, "; \t");
167            AP_DEBUG_ASSERT(type != NULL);
168            ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
169                          "Content-Type '%s' ...", r->content_type);
170            while (*type) {
171                /* Handle 'content-type;charset=...' correctly */
172                if (strncmp(*type, r->content_type, len) == 0
173                    && (*type)[len] == '\0') {
174                    ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
175                                  "... matched '%s'", *type);
176                    match = 1;
177                    break;
178                }
179                else {
180                    ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
181                                  "... did not match '%s'", *type);
182                }
183                type++;
184            }
185            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
186                          "Content-Type condition for '%s' %s",
187                          provider->frec->name,
188                          match ? "matched" : "did not match");
189        }
190        else {
191            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
192                          "Content-Type condition for '%s' did not match: "
193                          "no Content-Type", provider->frec->name);
194        }
195
196        if (match) {
197            /* condition matches this provider */
198#ifndef NO_PROTOCOL
199            /* check protocol
200             *
201             * FIXME:
202             * This is a quick hack and almost certainly buggy.
203             * The idea is that by putting this in mod_filter, we relieve
204             * filter implementations of the burden of fixing up HTTP headers
205             * for cases that are routinely affected by filters.
206             *
207             * Default is ALWAYS to do nothing, so as not to tread on the
208             * toes of filters which want to do it themselves.
209             *
210             */
211            proto_flags = provider->frec->proto_flags;
212
213            /* some specific things can't happen in a proxy */
214            if (r->proxyreq) {
215                if (proto_flags & AP_FILTER_PROTO_NO_PROXY) {
216                    /* can't use this provider; try next */
217                    continue;
218                }
219
220                if (proto_flags & AP_FILTER_PROTO_TRANSFORM) {
221                    const char *str = apr_table_get(r->headers_out,
222                                                    "Cache-Control");
223                    if (str) {
224                        if (ap_strcasestr(str, "no-transform")) {
225                            /* can't use this provider; try next */
226                            continue;
227                        }
228                    }
229                    apr_table_addn(r->headers_out, "Warning",
230                                   apr_psprintf(r->pool,
231                                                "214 %s Transformation applied",
232                                                r->hostname));
233                }
234            }
235
236            /* things that are invalidated if the filter transforms content */
237            if (proto_flags & AP_FILTER_PROTO_CHANGE) {
238                apr_table_unset(r->headers_out, "Content-MD5");
239                apr_table_unset(r->headers_out, "ETag");
240                if (proto_flags & AP_FILTER_PROTO_CHANGE_LENGTH) {
241                    apr_table_unset(r->headers_out, "Content-Length");
242                }
243            }
244
245            /* no-cache is for a filter that has different effect per-hit */
246            if (proto_flags & AP_FILTER_PROTO_NO_CACHE) {
247                apr_table_unset(r->headers_out, "Last-Modified");
248                apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
249            }
250
251            if (proto_flags & AP_FILTER_PROTO_NO_BYTERANGE) {
252                apr_table_setn(r->headers_out, "Accept-Ranges", "none");
253            }
254            else if (rctx && rctx->range) {
255                /* restore range header we saved earlier */
256                apr_table_setn(r->headers_in, "Range", rctx->range);
257                rctx->range = NULL;
258            }
259#endif
260            for (pctx = ctx->init_ctx; pctx; pctx = pctx->next) {
261                if (pctx->provider == provider) {
262                    ctx->fctx = pctx->ctx ;
263                }
264            }
265            ctx->func = provider->frec->filter_func.out_func;
266            return 1;
267        }
268    }
269
270    /* No provider matched */
271    return 0;
272}
273
274static apr_status_t filter_harness(ap_filter_t *f, apr_bucket_brigade *bb)
275{
276    apr_status_t ret;
277#ifndef NO_PROTOCOL
278    const char *cachecontrol;
279#endif
280    harness_ctx *ctx = f->ctx;
281    ap_filter_rec_t *filter = f->frec;
282
283    if (f->r->status != 200
284        && !apr_table_get(f->r->subprocess_env, "filter-errordocs")) {
285        ap_remove_output_filter(f);
286        return ap_pass_brigade(f->next, bb);
287    }
288
289    filter_trace(f->c, filter->debug, f->frec->name, bb);
290
291    /* look up a handler function if we haven't already set it */
292    if (!ctx->func) {
293#ifndef NO_PROTOCOL
294        if (f->r->proxyreq) {
295            if (filter->proto_flags & AP_FILTER_PROTO_NO_PROXY) {
296                ap_remove_output_filter(f);
297                return ap_pass_brigade(f->next, bb);
298            }
299
300            if (filter->proto_flags & AP_FILTER_PROTO_TRANSFORM) {
301                cachecontrol = apr_table_get(f->r->headers_out,
302                                             "Cache-Control");
303                if (cachecontrol) {
304                    if (ap_strcasestr(cachecontrol, "no-transform")) {
305                        ap_remove_output_filter(f);
306                        return ap_pass_brigade(f->next, bb);
307                    }
308                }
309            }
310        }
311#endif
312        if (!filter_lookup(f, filter)) {
313            ap_remove_output_filter(f);
314            return ap_pass_brigade(f->next, bb);
315        }
316        AP_DEBUG_ASSERT(ctx->func != NULL);
317    }
318
319    /* call the content filter with its own context, then restore our
320     * context
321     */
322    f->ctx = ctx->fctx;
323    ret = ctx->func(f, bb);
324    ctx->fctx = f->ctx;
325    f->ctx = ctx;
326
327    return ret;
328}
329
330#ifndef NO_PROTOCOL
331static const char *filter_protocol(cmd_parms *cmd, void *CFG, const char *fname,
332                                   const char *pname, const char *proto)
333{
334    static const char *sep = ";, \t";
335    char *arg;
336    char *tok = 0;
337    unsigned int flags = 0;
338    mod_filter_cfg *cfg = CFG;
339    ap_filter_provider_t *provider = NULL;
340    ap_filter_rec_t *filter = apr_hash_get(cfg->live_filters, fname,
341                                           APR_HASH_KEY_STRING);
342
343    if (!filter) {
344        return "FilterProtocol: No such filter";
345    }
346
347    /* Fixup the args: it's really pname that's optional */
348    if (proto == NULL) {
349        proto = pname;
350        pname = NULL;
351    }
352    else {
353        /* Find provider */
354        for (provider = filter->providers; provider; provider = provider->next){
355            if (!strcasecmp(provider->frec->name, pname)) {
356                break;
357            }
358        }
359        if (!provider) {
360            return "FilterProtocol: No such provider for this filter";
361        }
362    }
363
364    /* Now set flags from our args */
365    for (arg = apr_strtok(apr_pstrdup(cmd->pool, proto), sep, &tok);
366         arg; arg = apr_strtok(NULL, sep, &tok)) {
367
368        if (!strcasecmp(arg, "change=yes")) {
369            flags |= AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH;
370        }
371        if (!strcasecmp(arg, "change=no")) {
372            flags &= ~(AP_FILTER_PROTO_CHANGE | AP_FILTER_PROTO_CHANGE_LENGTH);
373        }
374        else if (!strcasecmp(arg, "change=1:1")) {
375            flags |= AP_FILTER_PROTO_CHANGE;
376        }
377        else if (!strcasecmp(arg, "byteranges=no")) {
378            flags |= AP_FILTER_PROTO_NO_BYTERANGE;
379        }
380        else if (!strcasecmp(arg, "proxy=no")) {
381            flags |= AP_FILTER_PROTO_NO_PROXY;
382        }
383        else if (!strcasecmp(arg, "proxy=transform")) {
384            flags |= AP_FILTER_PROTO_TRANSFORM;
385        }
386        else if (!strcasecmp(arg, "cache=no")) {
387            flags |= AP_FILTER_PROTO_NO_CACHE;
388        }
389    }
390
391    if (pname) {
392        provider->frec->proto_flags = flags;
393    }
394    else {
395        filter->proto_flags = flags;
396    }
397
398    return NULL;
399}
400#endif
401
402static const char *filter_declare(cmd_parms *cmd, void *CFG, const char *fname,
403                                  const char *place)
404{
405    mod_filter_cfg *cfg = (mod_filter_cfg *)CFG;
406    ap_filter_rec_t *filter;
407
408    filter = apr_pcalloc(cmd->pool, sizeof(ap_filter_rec_t));
409    apr_hash_set(cfg->live_filters, fname, APR_HASH_KEY_STRING, filter);
410
411    filter->name = fname;
412    filter->filter_init_func = filter_init;
413    filter->filter_func.out_func = filter_harness;
414    filter->ftype = AP_FTYPE_RESOURCE;
415    filter->next = NULL;
416
417    if (place) {
418        if (!strcasecmp(place, "CONTENT_SET")) {
419            filter->ftype = AP_FTYPE_CONTENT_SET;
420        }
421        else if (!strcasecmp(place, "PROTOCOL")) {
422            filter->ftype = AP_FTYPE_PROTOCOL;
423        }
424        else if (!strcasecmp(place, "CONNECTION")) {
425            filter->ftype = AP_FTYPE_CONNECTION;
426        }
427        else if (!strcasecmp(place, "NETWORK")) {
428            filter->ftype = AP_FTYPE_NETWORK;
429        }
430    }
431
432    return NULL;
433}
434
435static const char *add_filter(cmd_parms *cmd, void *CFG,
436                              const char *fname, const char *pname,
437                              const char *expr, const char **types)
438{
439    mod_filter_cfg *cfg = CFG;
440    ap_filter_provider_t *provider;
441    const char *c;
442    ap_filter_rec_t* frec;
443    ap_filter_rec_t* provider_frec;
444    ap_expr_info_t *node;
445    const char *err = NULL;
446
447    /* fname has been declared with DeclareFilter, so we can look it up */
448    frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
449
450    /* or if provider is mod_filter itself, we can also look it up */
451    if (!frec) {
452        c = filter_declare(cmd, CFG, fname, NULL);
453        if ( c ) {
454            return c;
455        }
456        frec = apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING);
457    }
458
459    if (!frec) {
460        return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
461    }
462
463    /* if provider has been registered, we can look it up */
464    provider_frec = ap_get_output_filter_handle(pname);
465    if (!provider_frec) {
466        return apr_psprintf(cmd->pool, "Unknown filter provider %s", pname);
467    }
468    provider = apr_palloc(cmd->pool, sizeof(ap_filter_provider_t));
469    if (expr) {
470        node = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
471        if (err) {
472            return apr_pstrcat(cmd->pool,
473                               "Error parsing FilterProvider expression:", err,
474                               NULL);
475        }
476        provider->expr = node;
477        provider->types = NULL;
478    }
479    else {
480        provider->types = types;
481        provider->expr = NULL;
482    }
483    provider->frec = provider_frec;
484    provider->next = frec->providers;
485    frec->providers = provider;
486    return NULL;
487}
488
489static const char *filter_provider(cmd_parms *cmd, void *CFG,
490                                   const char *fname, const char *pname,
491                                   const char *expr)
492{
493    return add_filter(cmd, CFG, fname, pname, expr, NULL);
494}
495
496static const char *filter_chain(cmd_parms *cmd, void *CFG, const char *arg)
497{
498    mod_filter_chain *p;
499    mod_filter_chain *q;
500    mod_filter_cfg *cfg = CFG;
501
502    switch (arg[0]) {
503    case '+':        /* add to end of chain */
504        p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
505        p->fname = arg+1;
506        if (cfg->chain) {
507            for (q = cfg->chain; q->next; q = q->next);
508            q->next = p;
509        }
510        else {
511            cfg->chain = p;
512        }
513        break;
514
515    case '@':        /* add to start of chain */
516        p = apr_palloc(cmd->pool, sizeof(mod_filter_chain));
517        p->fname = arg+1;
518        p->next = cfg->chain;
519        cfg->chain = p;
520        break;
521
522    case '-':        /* remove from chain */
523        if (cfg->chain) {
524            if (strcasecmp(cfg->chain->fname, arg+1)) {
525                for (p = cfg->chain; p->next; p = p->next) {
526                    if (!strcasecmp(p->next->fname, arg+1)) {
527                        p->next = p->next->next;
528                    }
529                }
530            }
531            else {
532                cfg->chain = cfg->chain->next;
533            }
534        }
535        break;
536
537    case '!':        /* Empty the chain */
538                     /** IG: Add a NULL provider to the beginning so that
539                      *  we can ensure that we'll empty everything before
540                      *  this when doing config merges later */
541        p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
542        p->fname = NULL;
543        cfg->chain = p;
544        break;
545
546    case '=':        /* initialise chain with this arg */
547                     /** IG: Prepend a NULL provider to the beginning as above */
548        p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
549        p->fname = NULL;
550        p->next = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
551        p->next->fname = arg+1;
552        cfg->chain = p;
553        break;
554
555    default:        /* add to end */
556        p = apr_pcalloc(cmd->pool, sizeof(mod_filter_chain));
557        p->fname = arg;
558        if (cfg->chain) {
559            for (q = cfg->chain; q->next; q = q->next);
560            q->next = p;
561        }
562        else {
563            cfg->chain = p;
564        }
565        break;
566    }
567
568    return NULL;
569}
570
571static const char *filter_bytype1(cmd_parms *cmd, void *CFG,
572                                  const char *pname, const char **types)
573{
574    const char *rv;
575    const char *fname;
576    int seen_name = 0;
577    mod_filter_cfg *cfg = CFG;
578
579    /* construct fname from name */
580    fname = apr_pstrcat(cmd->pool, "BYTYPE:", pname, NULL);
581
582    /* check whether this is already registered, in which case
583     * it's already in the filter chain
584     */
585    if (apr_hash_get(cfg->live_filters, fname, APR_HASH_KEY_STRING)) {
586        seen_name = 1;
587    }
588
589    rv = add_filter(cmd, CFG, fname, pname, NULL, types);
590
591    /* If it's the first time through, add to filterchain */
592    if (rv == NULL && !seen_name) {
593        rv = filter_chain(cmd, CFG, fname);
594    }
595    return rv;
596}
597
598static const char *filter_bytype(cmd_parms *cmd, void *CFG,
599                                 int argc, char *const argv[])
600{
601    /* back compatibility, need to parse multiple components in filter name */
602    char *pname;
603    char *strtok_state = NULL;
604    char *name;
605    const char **types;
606    const char *rv = NULL;
607    if (argc < 2)
608        return "AddOutputFilterByType requires at least two arguments";
609    name = apr_pstrdup(cmd->temp_pool, argv[0]);
610    types = apr_palloc(cmd->pool, argc * sizeof(char *));
611    memcpy(types, &argv[1], (argc - 1) * sizeof(char *));
612    types[argc-1] = NULL;
613    for (pname = apr_strtok(name, ";", &strtok_state);
614         pname != NULL && rv == NULL;
615         pname = apr_strtok(NULL, ";", &strtok_state)) {
616        rv = filter_bytype1(cmd, CFG, pname, types);
617    }
618    return rv;
619}
620
621static const char *filter_debug(cmd_parms *cmd, void *CFG, const char *fname,
622                                const char *level)
623{
624    mod_filter_cfg *cfg = CFG;
625    ap_filter_rec_t *frec = apr_hash_get(cfg->live_filters, fname,
626                                         APR_HASH_KEY_STRING);
627    if (!frec) {
628        return apr_psprintf(cmd->pool, "Undeclared smart filter %s", fname);
629    }
630    frec->debug = atoi(level);
631
632    return NULL;
633}
634
635static void filter_insert(request_rec *r)
636{
637    mod_filter_chain *p;
638    ap_filter_rec_t *filter;
639    mod_filter_cfg *cfg = ap_get_module_config(r->per_dir_config,
640                                               &filter_module);
641#ifndef NO_PROTOCOL
642    int ranges = 1;
643    mod_filter_ctx *ctx = apr_pcalloc(r->pool, sizeof(mod_filter_ctx));
644    ap_set_module_config(r->request_config, &filter_module, ctx);
645#endif
646
647    /** IG: Now that we've merged to the final config, go one last time
648     *  through the chain, and prune out the NULL filters */
649
650    for (p = cfg->chain; p; p = p->next) {
651        if (p->fname == NULL)
652            cfg->chain = p->next;
653    }
654
655    for (p = cfg->chain; p; p = p->next) {
656        filter = apr_hash_get(cfg->live_filters, p->fname, APR_HASH_KEY_STRING);
657        if (filter == NULL) {
658            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01380)
659                          "Unknown filter %s not added", p->fname);
660            continue;
661        }
662        ap_add_output_filter_handle(filter, NULL, r, r->connection);
663#ifndef NO_PROTOCOL
664        if (ranges && (filter->proto_flags
665                       & (AP_FILTER_PROTO_NO_BYTERANGE
666                          | AP_FILTER_PROTO_CHANGE_LENGTH))) {
667            ctx->range = apr_table_get(r->headers_in, "Range");
668            apr_table_unset(r->headers_in, "Range");
669            ranges = 0;
670        }
671#endif
672    }
673
674    return;
675}
676
677static void filter_hooks(apr_pool_t *pool)
678{
679    ap_hook_insert_filter(filter_insert, NULL, NULL, APR_HOOK_MIDDLE);
680}
681
682static void *filter_config(apr_pool_t *pool, char *x)
683{
684    mod_filter_cfg *cfg = apr_palloc(pool, sizeof(mod_filter_cfg));
685    cfg->live_filters = apr_hash_make(pool);
686    cfg->chain = NULL;
687    return cfg;
688}
689
690static void *filter_merge(apr_pool_t *pool, void *BASE, void *ADD)
691{
692    mod_filter_cfg *base = BASE;
693    mod_filter_cfg *add = ADD;
694    mod_filter_chain *savelink = 0;
695    mod_filter_chain *newlink;
696    mod_filter_chain *p;
697    mod_filter_cfg *conf = apr_palloc(pool, sizeof(mod_filter_cfg));
698
699    conf->live_filters = apr_hash_overlay(pool, add->live_filters,
700                                          base->live_filters);
701    if (base->chain && add->chain) {
702        for (p = base->chain; p; p = p->next) {
703            newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
704            if (newlink->fname == NULL) {
705                conf->chain = savelink = newlink;
706            }
707            else if (savelink) {
708                savelink->next = newlink;
709                savelink = newlink;
710            }
711            else {
712                conf->chain = savelink = newlink;
713            }
714        }
715
716        for (p = add->chain; p; p = p->next) {
717            newlink = apr_pmemdup(pool, p, sizeof(mod_filter_chain));
718            /** Filter out merged chain resets */
719            if (newlink->fname == NULL) {
720                conf->chain = savelink = newlink;
721            }
722            else if (savelink) {
723                savelink->next = newlink;
724                savelink = newlink;
725            }
726            else {
727                conf->chain = savelink = newlink;
728            }
729        }
730    }
731    else if (add->chain) {
732        conf->chain = add->chain;
733    }
734    else {
735        conf->chain = base->chain;
736    }
737
738    return conf;
739}
740
741static const command_rec filter_cmds[] = {
742    AP_INIT_TAKE12("FilterDeclare", filter_declare, NULL, OR_OPTIONS,
743        "filter-name [filter-type]"),
744    AP_INIT_TAKE3("FilterProvider", filter_provider, NULL, OR_OPTIONS,
745        "filter-name provider-name match-expression"),
746    AP_INIT_ITERATE("FilterChain", filter_chain, NULL, OR_OPTIONS,
747        "list of filter names with optional [+-=!@]"),
748    AP_INIT_TAKE2("FilterTrace", filter_debug, NULL, RSRC_CONF | ACCESS_CONF,
749        "filter-name debug-level"),
750    AP_INIT_TAKE_ARGV("AddOutputFilterByType", filter_bytype, NULL, OR_FILEINFO,
751        "output filter name followed by one or more content-types"),
752#ifndef NO_PROTOCOL
753    AP_INIT_TAKE23("FilterProtocol", filter_protocol, NULL, OR_OPTIONS,
754        "filter-name [provider-name] protocol-args"),
755#endif
756    { NULL }
757};
758
759AP_DECLARE_MODULE(filter) = {
760    STANDARD20_MODULE_STUFF,
761    filter_config,
762    filter_merge,
763    NULL,
764    NULL,
765    filter_cmds,
766    filter_hooks
767};
768