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.
18
* mod_ext_filter allows Unix-style filters to filter http content.
22
#include "http_config.h"
24
#include "http_protocol.h"
26
#include "http_core.h"
27
#include "apr_buckets.h"
28
#include "util_filter.h"
29
#include "util_script.h"
30
#include "util_time.h"
31
#include "apr_strings.h"
35
#define APR_WANT_STRFUNC
38
typedef struct ef_server_t {
43
typedef struct ef_filter_t {
45
enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
48
const char *enable_env;
49
const char *disable_env;
51
const char *intype; /* list of IMTs we process (well, just one for now) */
52
#define INTYPE_ALL (char *)1
53
const char *outtype; /* IMT of filtered output */
54
#define OUTTYPE_UNCHANGED (char *)1
55
int preserves_content_length;
58
typedef struct ef_dir_t {
63
typedef struct ef_ctx_t {
66
apr_procattr_t *procattr;
70
#if APR_FILES_AS_SOCKETS
71
apr_pollset_t *pollset;
75
module AP_MODULE_DECLARE_DATA ext_filter_module;
76
static const server_rec *main_server;
78
static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
79
static apr_status_t ef_input_filter(ap_filter_t *, apr_bucket_brigade *,
80
ap_input_mode_t, apr_read_type_e,
83
#define DBGLVL_SHOWOPTIONS 1
84
#define DBGLVL_ERRORCHECK 2
87
#define ERRFN_USERDATA_KEY "EXTFILTCHILDERRFN"
89
static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
91
ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
99
static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
103
conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
105
conf->h = apr_hash_make(conf->p);
109
static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
111
ef_dir_t *a = (ef_dir_t *)apr_pcalloc (p, sizeof(ef_dir_t));
112
ef_dir_t *base = (ef_dir_t *)basev, *over = (ef_dir_t *)overridesv;
114
if (over->debug != -1) { /* if admin coded something... */
115
a->debug = over->debug;
118
a->debug = base->debug;
121
if (over->log_stderr != -1) { /* if admin coded something... */
122
a->log_stderr = over->log_stderr;
125
a->log_stderr = base->log_stderr;
131
static const char *add_options(cmd_parms *cmd, void *in_dc,
134
ef_dir_t *dc = in_dc;
136
if (!strncasecmp(arg, "DebugLevel=", 11)) {
137
dc->debug = atoi(arg + 11);
139
else if (!strcasecmp(arg, "LogStderr")) {
142
else if (!strcasecmp(arg, "NoLogStderr")) {
146
return apr_pstrcat(cmd->temp_pool,
147
"Invalid ExtFilterOptions option: ",
155
static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
158
const char *start = *args + 1;
163
++*args; /* move past leading " */
164
/* find true end of args string (accounting for escaped quotes) */
165
while (**args && (**args != '"' || (**args == '"' && escaping))) {
169
else if (**args == '\\') {
175
return "Expected cmd= delimiter";
177
/* copy *just* the arg string for parsing, */
178
parms = apr_pstrndup(p, start, *args - start);
179
++*args; /* move past trailing " */
181
/* parse and tokenize the args. */
182
rv = apr_tokenize_to_argv(parms, &(filter->args), p);
183
if (rv != APR_SUCCESS) {
184
return "cmd= parse error";
190
/* Allocate space for two argv pointers and parse the args. */
191
filter->args = (char **)apr_palloc(p, 2 * sizeof(char *));
192
filter->args[0] = ap_getword_white(p, args);
193
filter->args[1] = NULL; /* end of args */
195
if (!filter->args[0]) {
196
return "Invalid cmd= parameter";
198
filter->command = filter->args[0];
203
static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
205
ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
211
name = ap_getword_white(cmd->pool, &args);
213
return "Filter name not found";
216
if (apr_hash_get(conf->h, name, APR_HASH_KEY_STRING)) {
217
return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
221
filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
223
filter->mode = OUTPUT_FILTER;
224
filter->ftype = AP_FTYPE_RESOURCE;
225
apr_hash_set(conf->h, name, APR_HASH_KEY_STRING, filter);
228
while (apr_isspace(*args)) {
232
/* Nasty parsing... I wish I could simply use ap_getword_white()
233
* here and then look at the token, but ap_getword_white() doesn't
234
* do the right thing when we have cmd="word word word"
236
if (!strncasecmp(args, "preservescontentlength", 22)) {
237
token = ap_getword_white(cmd->pool, &args);
238
if (!strcasecmp(token, "preservescontentlength")) {
239
filter->preserves_content_length = 1;
242
return apr_psprintf(cmd->pool,
243
"mangled argument `%s'",
249
if (!strncasecmp(args, "mode=", 5)) {
251
token = ap_getword_white(cmd->pool, &args);
252
if (!strcasecmp(token, "output")) {
253
filter->mode = OUTPUT_FILTER;
255
else if (!strcasecmp(token, "input")) {
256
filter->mode = INPUT_FILTER;
259
return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
265
if (!strncasecmp(args, "ftype=", 6)) {
267
token = ap_getword_white(cmd->pool, &args);
268
filter->ftype = atoi(token);
272
if (!strncasecmp(args, "enableenv=", 10)) {
274
token = ap_getword_white(cmd->pool, &args);
275
filter->enable_env = token;
279
if (!strncasecmp(args, "disableenv=", 11)) {
281
token = ap_getword_white(cmd->pool, &args);
282
filter->disable_env = token;
286
if (!strncasecmp(args, "intype=", 7)) {
288
filter->intype = ap_getword_white(cmd->pool, &args);
292
if (!strncasecmp(args, "outtype=", 8)) {
294
filter->outtype = ap_getword_white(cmd->pool, &args);
298
if (!strncasecmp(args, "cmd=", 4)) {
300
if ((token = parse_cmd(cmd->pool, &args, filter))) {
306
return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
310
/* parsing is done... register the filter
312
if (filter->mode == OUTPUT_FILTER) {
313
/* XXX need a way to ensure uniqueness among all filters */
314
ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
316
else if (filter->mode == INPUT_FILTER) {
317
/* XXX need a way to ensure uniqueness among all filters */
318
ap_register_input_filter(filter->name, ef_input_filter, NULL, filter->ftype);
321
ap_assert(1 != 1); /* we set the field wrong somehow */
327
static const command_rec cmds[] =
329
AP_INIT_ITERATE("ExtFilterOptions",
332
ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
333
"valid options: DebugLevel=n, LogStderr, NoLogStderr"),
334
AP_INIT_RAW_ARGS("ExtFilterDefine",
338
"Define an external filter"),
342
static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s)
344
main_server = main_s;
348
static void register_hooks(apr_pool_t *p)
350
ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
353
static apr_status_t set_resource_limits(request_rec *r,
354
apr_procattr_t *procattr)
356
#if defined(RLIMIT_CPU) || defined(RLIMIT_NPROC) || \
357
defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
358
core_dir_config *conf =
359
(core_dir_config *)ap_get_module_config(r->per_dir_config,
364
rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu);
365
ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
367
#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
368
rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem);
369
ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
372
rv = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
373
ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
376
#endif /* if at least one limit defined */
381
static apr_status_t ef_close_file(void *vfile)
383
return apr_file_close(vfile);
386
static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
390
apr_file_t *stderr_log;
392
char time_str[APR_CTIME_LEN];
394
apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
396
apr_file_open_stderr(&stderr_log, pool);
397
ap_recent_ctime(time_str, apr_time_now());
398
apr_file_printf(stderr_log,
399
"[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
401
r->connection->remote_ip,
403
apr_strerror(err, errbuf, sizeof(errbuf)),
407
/* init_ext_filter_process: get the external filter process going
408
* This is per-filter-instance (i.e., per-request) initialization.
410
static apr_status_t init_ext_filter_process(ap_filter_t *f)
412
ef_ctx_t *ctx = f->ctx;
414
ef_dir_t *dc = ctx->dc;
415
const char * const *env;
417
ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
419
rc = apr_procattr_create(&ctx->procattr, ctx->p);
420
ap_assert(rc == APR_SUCCESS);
422
rc = apr_procattr_io_set(ctx->procattr,
426
ap_assert(rc == APR_SUCCESS);
428
rc = set_resource_limits(f->r, ctx->procattr);
429
ap_assert(rc == APR_SUCCESS);
431
if (dc->log_stderr > 0) {
432
rc = apr_procattr_child_err_set(ctx->procattr,
433
f->r->server->error_log, /* stderr in child */
435
ap_assert(rc == APR_SUCCESS);
438
rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
439
ap_assert(rc == APR_SUCCESS);
440
apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
442
if (dc->debug >= DBGLVL_ERRORCHECK) {
443
rc = apr_procattr_error_check_set(ctx->procattr, 1);
444
ap_assert(rc == APR_SUCCESS);
447
/* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
448
* and QUERY_STRING_UNESCAPED
450
ap_add_cgi_vars(f->r);
451
ap_add_common_vars(f->r);
452
apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
453
apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
455
/* QUERY_STRING is added by ap_add_cgi_vars */
456
char *arg_copy = apr_pstrdup(f->r->pool, f->r->args);
457
ap_unescape_url(arg_copy);
458
apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED",
459
ap_escape_shell_cmd(f->r->pool, arg_copy));
462
env = (const char * const *) ap_create_environment(ctx->p,
463
f->r->subprocess_env);
465
rc = apr_proc_create(ctx->proc,
466
ctx->filter->command,
467
(const char * const *)ctx->filter->args,
468
env, /* environment */
471
if (rc != APR_SUCCESS) {
472
ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
473
"couldn't create child process to run `%s'",
474
ctx->filter->command);
478
apr_pool_note_subprocess(ctx->p, ctx->proc, APR_KILL_AFTER_TIMEOUT);
480
/* We don't want the handle to the child's stdin inherited by any
481
* other processes created by httpd. Otherwise, when we close our
482
* handle, the child won't see EOF because another handle will still
486
apr_pool_cleanup_register(ctx->p, ctx->proc->in,
487
apr_pool_cleanup_null, /* other mechanism */
490
#if APR_FILES_AS_SOCKETS
492
apr_pollfd_t pfd = { 0 };
494
rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0);
495
ap_assert(rc == APR_SUCCESS);
498
pfd.desc_type = APR_POLL_FILE;
499
pfd.reqevents = APR_POLLOUT;
500
pfd.desc.f = ctx->proc->in;
501
rc = apr_pollset_add(ctx->pollset, &pfd);
502
ap_assert(rc == APR_SUCCESS);
504
pfd.reqevents = APR_POLLIN;
505
pfd.desc.f = ctx->proc->out;
506
rc = apr_pollset_add(ctx->pollset, &pfd);
507
ap_assert(rc == APR_SUCCESS);
514
static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
516
const char *debug_str = dc->debug == -1 ?
517
"DebugLevel=0" : apr_psprintf(p, "DebugLevel=%d", dc->debug);
518
const char *log_stderr_str = dc->log_stderr < 1 ?
519
"NoLogStderr" : "LogStderr";
520
const char *preserve_content_length_str = filter->preserves_content_length ?
521
"PreservesContentLength" : "!PreserveContentLength";
522
const char *intype_str = !filter->intype ?
523
"*/*" : filter->intype;
524
const char *outtype_str = !filter->outtype ?
525
"(unchanged)" : filter->outtype;
527
return apr_psprintf(p,
528
"ExtFilterOptions %s %s %s ExtFilterInType %s "
529
"ExtFilterOuttype %s",
530
debug_str, log_stderr_str, preserve_content_length_str,
531
intype_str, outtype_str);
534
static ef_filter_t *find_filter_def(const server_rec *s, const char *fname)
539
sc = ap_get_module_config(s->module_config, &ext_filter_module);
540
f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
541
if (!f && s != main_server) {
543
sc = ap_get_module_config(s->module_config, &ext_filter_module);
544
f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
549
static apr_status_t init_filter_instance(ap_filter_t *f)
555
f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
556
dc = ap_get_module_config(f->r->per_dir_config,
559
/* look for the user-defined filter */
560
ctx->filter = find_filter_def(f->r->server, f->frec->name);
562
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
563
"couldn't find definition of filter '%s'",
568
if (ctx->filter->intype &&
569
ctx->filter->intype != INTYPE_ALL) {
572
if (ctx->filter->mode == INPUT_FILTER) {
573
ctypes = apr_table_get(f->r->headers_in, "Content-Type");
576
ctypes = f->r->content_type;
580
const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
582
if (strcasecmp(ctx->filter->intype, ctype)) {
583
/* wrong IMT for us; don't mess with the output */
591
if (ctx->filter->enable_env &&
592
!apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
593
/* an environment variable that enables the filter isn't set; bail */
596
if (ctx->filter->disable_env &&
597
apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) {
598
/* an environment variable that disables the filter is set; bail */
602
rv = init_ext_filter_process(f);
603
if (rv != APR_SUCCESS) {
606
if (ctx->filter->outtype &&
607
ctx->filter->outtype != OUTTYPE_UNCHANGED) {
608
ap_set_content_type(f->r, ctx->filter->outtype);
610
if (ctx->filter->preserves_content_length != 1) {
611
/* nasty, but needed to avoid confusing the browser
613
apr_table_unset(f->r->headers_out, "Content-Length");
617
if (dc->debug >= DBGLVL_SHOWOPTIONS) {
618
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
619
"%sfiltering `%s' of type `%s' through `%s', cfg %s",
620
ctx->noop ? "NOT " : "",
621
f->r->uri ? f->r->uri : f->r->filename,
622
f->r->content_type ? f->r->content_type : "(unspecified)",
623
ctx->filter->command,
624
get_cfg_string(dc, ctx->filter, f->r->pool));
630
/* drain_available_output():
632
* if any data is available from the filter, read it and append it
633
* to the the bucket brigade
635
static apr_status_t drain_available_output(ap_filter_t *f,
636
apr_bucket_brigade *bb)
638
request_rec *r = f->r;
639
conn_rec *c = r->connection;
640
ef_ctx_t *ctx = f->ctx;
641
ef_dir_t *dc = ctx->dc;
649
rv = apr_file_read(ctx->proc->out,
652
if ((rv && !APR_STATUS_IS_EAGAIN(rv)) ||
653
dc->debug >= DBGLVL_GORY) {
654
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
655
"apr_file_read(child output), len %" APR_SIZE_T_FMT,
658
if (rv != APR_SUCCESS) {
661
b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
662
APR_BRIGADE_INSERT_TAIL(bb, b);
665
/* we should never get here; if we do, a bogus error message would be
666
* the least of our problems
668
return APR_ANONYMOUS;
671
static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
672
apr_size_t len, apr_bucket_brigade *bb)
674
ef_ctx_t *ctx = f->ctx;
675
ef_dir_t *dc = ctx->dc;
677
apr_size_t bytes_written = 0;
681
tmplen = len - bytes_written;
682
rv = apr_file_write(ctx->proc->in,
683
(const char *)data + bytes_written,
685
bytes_written += tmplen;
686
if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
687
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
688
"apr_file_write(child input), len %" APR_SIZE_T_FMT,
692
if (APR_STATUS_IS_EAGAIN(rv)) {
693
/* XXX handle blocking conditions here... if we block, we need
694
* to read data from the child process and pass it down to the
697
rv = drain_available_output(f, bb);
698
if (APR_STATUS_IS_EAGAIN(rv)) {
699
#if APR_FILES_AS_SOCKETS
701
const apr_pollfd_t *pdesc;
703
rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
704
&num_events, &pdesc);
705
if (rv || dc->debug >= DBGLVL_GORY) {
706
ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
707
rv, f->r, "apr_pollset_poll()");
709
if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
710
/* some error such as APR_TIMEUP */
713
#else /* APR_FILES_AS_SOCKETS */
714
/* Yuck... I'd really like to wait until I can read
715
* or write, but instead I have to sleep and try again
717
apr_sleep(100000); /* 100 milliseconds */
718
if (dc->debug >= DBGLVL_GORY) {
719
ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
720
0, f->r, "apr_sleep()");
722
#endif /* APR_FILES_AS_SOCKETS */
724
else if (rv != APR_SUCCESS) {
728
} while (bytes_written < len);
732
/* ef_unified_filter:
734
* runs the bucket brigade bb through the filter and puts the result into
735
* bb, dropping the previous content of bb (the input)
738
static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
740
request_rec *r = f->r;
741
conn_rec *c = r->connection;
742
ef_ctx_t *ctx = f->ctx;
749
apr_bucket *eos = NULL;
750
apr_bucket_brigade *bb_tmp;
753
bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
755
for (b = APR_BRIGADE_FIRST(bb);
756
b != APR_BRIGADE_SENTINEL(bb);
757
b = APR_BUCKET_NEXT(b))
759
if (APR_BUCKET_IS_EOS(b)) {
764
rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
765
if (rv != APR_SUCCESS) {
766
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "apr_bucket_read()");
770
/* Good cast, we just tested len isn't negative */
772
(rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
778
apr_brigade_cleanup(bb);
779
APR_BRIGADE_CONCAT(bb, bb_tmp);
780
apr_brigade_destroy(bb_tmp);
783
/* close the child's stdin to signal that no more data is coming;
784
* that will cause the child to finish generating output
786
if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
787
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
788
"apr_file_close(child input)");
791
/* since we've seen eos and closed the child's stdin, set the proper pipe
792
* timeout; we don't care if we don't return from apr_file_read() for a while...
794
rv = apr_file_pipe_timeout_set(ctx->proc->out,
797
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
798
"apr_file_pipe_timeout_set(child output)");
805
rv = apr_file_read(ctx->proc->out,
808
if ((rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv)) ||
809
dc->debug >= DBGLVL_GORY) {
810
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
811
"apr_file_read(child output), len %" APR_SIZE_T_FMT,
814
if (APR_STATUS_IS_EAGAIN(rv)) {
816
/* should not occur, because we have an APR timeout in place */
817
AP_DEBUG_ASSERT(1 != 1);
822
if (rv == APR_SUCCESS) {
823
b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
824
APR_BRIGADE_INSERT_TAIL(bb, b);
826
} while (rv == APR_SUCCESS);
828
if (!APR_STATUS_IS_EOF(rv)) {
833
b = apr_bucket_eos_create(c->bucket_alloc);
834
APR_BRIGADE_INSERT_TAIL(bb, b);
840
static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
842
request_rec *r = f->r;
843
ef_ctx_t *ctx = f->ctx;
847
if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
853
ap_remove_output_filter(f);
854
return ap_pass_brigade(f->next, bb);
857
rv = ef_unified_filter(f, bb);
858
if (rv != APR_SUCCESS) {
859
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
860
"ef_unified_filter() failed");
863
if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
864
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
865
"ap_pass_brigade() failed");
870
static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
871
ap_input_mode_t mode, apr_read_type_e block,
874
ef_ctx_t *ctx = f->ctx;
878
if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
885
ap_remove_input_filter(f);
886
return ap_get_brigade(f->next, bb, mode, block, readbytes);
889
rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
890
if (rv != APR_SUCCESS) {
894
rv = ef_unified_filter(f, bb);
898
module AP_MODULE_DECLARE_DATA ext_filter_module =
900
STANDARD20_MODULE_STUFF,
903
create_ef_server_conf,