]> granicus.if.org Git - apache/blob - modules/filters/mod_ext_filter.c
e328ee478006a919a2cdc20ea7d1ab5ce521445c
[apache] / modules / filters / mod_ext_filter.c
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 /*
18  * mod_ext_filter allows Unix-style filters to filter http content.
19  */
20
21 #include "httpd.h"
22 #include "http_config.h"
23 #include "http_log.h"
24 #include "http_protocol.h"
25 #define CORE_PRIVATE
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"
32 #include "apr_hash.h"
33 #include "apr_lib.h"
34 #include "apr_poll.h"
35 #define APR_WANT_STRFUNC
36 #include "apr_want.h"
37
38 typedef struct ef_server_t {
39     apr_pool_t *p;
40     apr_hash_t *h;
41 } ef_server_t;
42
43 typedef struct ef_filter_t {
44     const char *name;
45     enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
46     ap_filter_type ftype;
47     const char *command;
48     const char *enable_env;
49     const char *disable_env;
50     char **args;
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;
56 } ef_filter_t;
57
58 typedef struct ef_dir_t {
59     int debug;
60     int log_stderr;
61 } ef_dir_t;
62
63 typedef struct ef_ctx_t {
64     apr_pool_t *p;
65     apr_proc_t *proc;
66     apr_procattr_t *procattr;
67     ef_dir_t *dc;
68     ef_filter_t *filter;
69     int noop;
70 #if APR_FILES_AS_SOCKETS
71     apr_pollset_t *pollset;
72 #endif
73 } ef_ctx_t;
74
75 module AP_MODULE_DECLARE_DATA ext_filter_module;
76 static const server_rec *main_server;
77
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,
81                                     apr_off_t);
82
83 #define DBGLVL_SHOWOPTIONS         1
84 #define DBGLVL_ERRORCHECK          2
85 #define DBGLVL_GORY                9
86
87 #define ERRFN_USERDATA_KEY         "EXTFILTCHILDERRFN"
88
89 static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
90 {
91     ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
92
93     dc->debug = -1;
94     dc->log_stderr = -1;
95
96     return dc;
97 }
98
99 static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
100 {
101     ef_server_t *conf;
102
103     conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
104     conf->p = p;
105     conf->h = apr_hash_make(conf->p);
106     return conf;
107 }
108
109 static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
110 {
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;
113
114     if (over->debug != -1) {        /* if admin coded something... */
115         a->debug = over->debug;
116     }
117     else {
118         a->debug = base->debug;
119     }
120
121     if (over->log_stderr != -1) {   /* if admin coded something... */
122         a->log_stderr = over->log_stderr;
123     }
124     else {
125         a->log_stderr = base->log_stderr;
126     }
127
128     return a;
129 }
130
131 static const char *add_options(cmd_parms *cmd, void *in_dc,
132                                const char *arg)
133 {
134     ef_dir_t *dc = in_dc;
135
136     if (!strncasecmp(arg, "DebugLevel=", 11)) {
137         dc->debug = atoi(arg + 11);
138     }
139     else if (!strcasecmp(arg, "LogStderr")) {
140         dc->log_stderr = 1;
141     }
142     else if (!strcasecmp(arg, "NoLogStderr")) {
143         dc->log_stderr = 0;
144     }
145     else {
146         return apr_pstrcat(cmd->temp_pool,
147                            "Invalid ExtFilterOptions option: ",
148                            arg,
149                            NULL);
150     }
151
152     return NULL;
153 }
154
155 static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
156 {
157     if (**args == '"') {
158         const char *start = *args + 1;
159         char *parms;
160         int escaping = 0;
161         apr_status_t rv;
162
163         ++*args; /* move past leading " */
164         /* find true end of args string (accounting for escaped quotes) */
165         while (**args && (**args != '"' || (**args == '"' && escaping))) {
166             if (escaping) {
167                 escaping = 0;
168             }
169             else if (**args == '\\') {
170                 escaping = 1;
171             }
172             ++*args;
173         }
174         if (**args != '"') {
175             return "Expected cmd= delimiter";
176         }
177         /* copy *just* the arg string for parsing, */
178         parms = apr_pstrndup(p, start, *args - start);
179         ++*args; /* move past trailing " */
180
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";
185         }
186     }
187     else
188     {
189         /* simple path */
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 */
194     }
195     if (!filter->args[0]) {
196         return "Invalid cmd= parameter";
197     }
198     filter->command = filter->args[0];
199
200     return NULL;
201 }
202
203 static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
204 {
205     ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
206                                              &ext_filter_module);
207     const char *token;
208     const char *name;
209     char *normalized_name;
210     ef_filter_t *filter;
211
212     name = ap_getword_white(cmd->pool, &args);
213     if (!name) {
214         return "Filter name not found";
215     }
216
217     /* During request processing, we find information about the filter
218      * by looking up the filter name provided by core server in our
219      * hash table.  But the core server has normalized the filter
220      * name by converting it to lower case.  Thus, when adding the
221      * filter to our hash table we have to use lower case as well.
222      */
223     normalized_name = apr_pstrdup(cmd->pool, name);
224     ap_str_tolower(normalized_name);
225
226     if (apr_hash_get(conf->h, normalized_name, APR_HASH_KEY_STRING)) {
227         return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
228                             name);
229     }
230
231     filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
232     filter->name = name;
233     filter->mode = OUTPUT_FILTER;
234     filter->ftype = AP_FTYPE_RESOURCE;
235     apr_hash_set(conf->h, normalized_name, APR_HASH_KEY_STRING, filter);
236
237     while (*args) {
238         while (apr_isspace(*args)) {
239             ++args;
240         }
241
242         /* Nasty parsing...  I wish I could simply use ap_getword_white()
243          * here and then look at the token, but ap_getword_white() doesn't
244          * do the right thing when we have cmd="word word word"
245          */
246         if (!strncasecmp(args, "preservescontentlength", 22)) {
247             token = ap_getword_white(cmd->pool, &args);
248             if (!strcasecmp(token, "preservescontentlength")) {
249                 filter->preserves_content_length = 1;
250             }
251             else {
252                 return apr_psprintf(cmd->pool,
253                                     "mangled argument `%s'",
254                                     token);
255             }
256             continue;
257         }
258
259         if (!strncasecmp(args, "mode=", 5)) {
260             args += 5;
261             token = ap_getword_white(cmd->pool, &args);
262             if (!strcasecmp(token, "output")) {
263                 filter->mode = OUTPUT_FILTER;
264             }
265             else if (!strcasecmp(token, "input")) {
266                 filter->mode = INPUT_FILTER;
267             }
268             else {
269                 return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
270                                     token);
271             }
272             continue;
273         }
274
275         if (!strncasecmp(args, "ftype=", 6)) {
276             args += 6;
277             token = ap_getword_white(cmd->pool, &args);
278             filter->ftype = atoi(token);
279             continue;
280         }
281
282         if (!strncasecmp(args, "enableenv=", 10)) {
283             args += 10;
284             token = ap_getword_white(cmd->pool, &args);
285             filter->enable_env = token;
286             continue;
287         }
288
289         if (!strncasecmp(args, "disableenv=", 11)) {
290             args += 11;
291             token = ap_getword_white(cmd->pool, &args);
292             filter->disable_env = token;
293             continue;
294         }
295
296         if (!strncasecmp(args, "intype=", 7)) {
297             args += 7;
298             filter->intype = ap_getword_white(cmd->pool, &args);
299             continue;
300         }
301
302         if (!strncasecmp(args, "outtype=", 8)) {
303             args += 8;
304             filter->outtype = ap_getword_white(cmd->pool, &args);
305             continue;
306         }
307
308         if (!strncasecmp(args, "cmd=", 4)) {
309             args += 4;
310             if ((token = parse_cmd(cmd->pool, &args, filter))) {
311                 return token;
312             }
313             continue;
314         }
315
316         return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
317                             args);
318     }
319
320     /* parsing is done...  register the filter
321      */
322     if (filter->mode == OUTPUT_FILTER) {
323         /* XXX need a way to ensure uniqueness among all filters */
324         ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
325     }
326     else if (filter->mode == INPUT_FILTER) {
327         /* XXX need a way to ensure uniqueness among all filters */
328         ap_register_input_filter(filter->name, ef_input_filter, NULL, filter->ftype);
329     }
330     else {
331         ap_assert(1 != 1); /* we set the field wrong somehow */
332     }
333
334     return NULL;
335 }
336
337 static const command_rec cmds[] =
338 {
339     AP_INIT_ITERATE("ExtFilterOptions",
340                     add_options,
341                     NULL,
342                     ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
343                     "valid options: DebugLevel=n, LogStderr, NoLogStderr"),
344     AP_INIT_RAW_ARGS("ExtFilterDefine",
345                      define_filter,
346                      NULL,
347                      RSRC_CONF,
348                      "Define an external filter"),
349     {NULL}
350 };
351
352 static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s)
353 {
354     main_server = main_s;
355     return OK;
356 }
357
358 static void register_hooks(apr_pool_t *p)
359 {
360     ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
361 }
362
363 static apr_status_t set_resource_limits(request_rec *r,
364                                         apr_procattr_t *procattr)
365 {
366 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
367     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
368     core_dir_config *conf =
369         (core_dir_config *)ap_get_module_config(r->per_dir_config,
370                                                 &core_module);
371     apr_status_t rv;
372
373 #ifdef RLIMIT_CPU
374     rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu);
375     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
376 #endif
377 #if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
378     rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem);
379     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
380 #endif
381 #ifdef RLIMIT_NPROC
382     rv = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
383     ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
384 #endif
385
386 #endif /* if at least one limit defined */
387
388     return APR_SUCCESS;
389 }
390
391 static apr_status_t ef_close_file(void *vfile)
392 {
393     return apr_file_close(vfile);
394 }
395
396 static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
397 {
398     request_rec *r;
399     void *vr;
400     apr_file_t *stderr_log;
401     char errbuf[200];
402     char time_str[APR_CTIME_LEN];
403
404     apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
405     r = vr;
406     apr_file_open_stderr(&stderr_log, pool);
407     ap_recent_ctime(time_str, apr_time_now());
408     apr_file_printf(stderr_log,
409                     "[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
410                     time_str,
411                     r->connection->remote_ip,
412                     err,
413                     apr_strerror(err, errbuf, sizeof(errbuf)),
414                     description);
415 }
416
417 /* init_ext_filter_process: get the external filter process going
418  * This is per-filter-instance (i.e., per-request) initialization.
419  */
420 static apr_status_t init_ext_filter_process(ap_filter_t *f)
421 {
422     ef_ctx_t *ctx = f->ctx;
423     apr_status_t rc;
424     ef_dir_t *dc = ctx->dc;
425     const char * const *env;
426
427     ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
428
429     rc = apr_procattr_create(&ctx->procattr, ctx->p);
430     ap_assert(rc == APR_SUCCESS);
431
432     rc = apr_procattr_io_set(ctx->procattr,
433                             APR_CHILD_BLOCK,
434                             APR_CHILD_BLOCK,
435                             APR_CHILD_BLOCK);
436     ap_assert(rc == APR_SUCCESS);
437
438     rc = set_resource_limits(f->r, ctx->procattr);
439     ap_assert(rc == APR_SUCCESS);
440
441     if (dc->log_stderr > 0) {
442         rc = apr_procattr_child_err_set(ctx->procattr,
443                                       f->r->server->error_log, /* stderr in child */
444                                       NULL);
445         ap_assert(rc == APR_SUCCESS);
446     }
447
448     rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
449     ap_assert(rc == APR_SUCCESS);
450     apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
451
452     if (dc->debug >= DBGLVL_ERRORCHECK) {
453         rc = apr_procattr_error_check_set(ctx->procattr, 1);
454         ap_assert(rc == APR_SUCCESS);
455     }
456
457     /* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
458      * and QUERY_STRING_UNESCAPED
459      */
460     ap_add_cgi_vars(f->r);
461     ap_add_common_vars(f->r);
462     apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
463     apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
464     if (f->r->args) {
465             /* QUERY_STRING is added by ap_add_cgi_vars */
466         char *arg_copy = apr_pstrdup(f->r->pool, f->r->args);
467         ap_unescape_url(arg_copy);
468         apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED",
469                        ap_escape_shell_cmd(f->r->pool, arg_copy));
470     }
471
472     env = (const char * const *) ap_create_environment(ctx->p,
473                                                        f->r->subprocess_env);
474
475     rc = apr_proc_create(ctx->proc,
476                             ctx->filter->command,
477                             (const char * const *)ctx->filter->args,
478                             env, /* environment */
479                             ctx->procattr,
480                             ctx->p);
481     if (rc != APR_SUCCESS) {
482         ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
483                       "couldn't create child process to run `%s'",
484                       ctx->filter->command);
485         return rc;
486     }
487
488     rc = apr_file_pipe_timeout_set(ctx->proc->out, 0);
489     if (rc != APR_SUCCESS) {
490         ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
491                       "couldn't set child stdin pipe timeout to 0 for filter %s ",
492                       ctx->filter->name);
493         return rc;
494     }
495
496     apr_pool_note_subprocess(ctx->p, ctx->proc, APR_KILL_AFTER_TIMEOUT);
497
498     /* We don't want the handle to the child's stdin inherited by any
499      * other processes created by httpd.  Otherwise, when we close our
500      * handle, the child won't see EOF because another handle will still
501      * be open.
502      */
503
504     apr_pool_cleanup_register(ctx->p, ctx->proc->in,
505                          apr_pool_cleanup_null, /* other mechanism */
506                          ef_close_file);
507
508 #if APR_FILES_AS_SOCKETS
509     {
510         apr_pollfd_t pfd = { 0 };
511
512         rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0);
513         ap_assert(rc == APR_SUCCESS);
514
515         pfd.p         = ctx->p;
516         pfd.desc_type = APR_POLL_FILE;
517         pfd.reqevents = APR_POLLOUT;
518         pfd.desc.f    = ctx->proc->in;
519         rc = apr_pollset_add(ctx->pollset, &pfd);
520         ap_assert(rc == APR_SUCCESS);
521
522         pfd.reqevents = APR_POLLIN;
523         pfd.desc.f    = ctx->proc->out;
524         rc = apr_pollset_add(ctx->pollset, &pfd);
525         ap_assert(rc == APR_SUCCESS);
526     }
527 #endif
528
529     return APR_SUCCESS;
530 }
531
532 static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
533 {
534     const char *debug_str = dc->debug == -1 ?
535         "DebugLevel=0" : apr_psprintf(p, "DebugLevel=%d", dc->debug);
536     const char *log_stderr_str = dc->log_stderr < 1 ?
537         "NoLogStderr" : "LogStderr";
538     const char *preserve_content_length_str = filter->preserves_content_length ?
539         "PreservesContentLength" : "!PreserveContentLength";
540     const char *intype_str = !filter->intype ?
541         "*/*" : filter->intype;
542     const char *outtype_str = !filter->outtype ?
543         "(unchanged)" : filter->outtype;
544
545     return apr_psprintf(p,
546                         "ExtFilterOptions %s %s %s ExtFilterInType %s "
547                         "ExtFilterOuttype %s",
548                         debug_str, log_stderr_str, preserve_content_length_str,
549                         intype_str, outtype_str);
550 }
551
552 static ef_filter_t *find_filter_def(const server_rec *s, const char *fname)
553 {
554     ef_server_t *sc;
555     ef_filter_t *f;
556
557     sc = ap_get_module_config(s->module_config, &ext_filter_module);
558     f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
559     if (!f && s != main_server) {
560         s = main_server;
561         sc = ap_get_module_config(s->module_config, &ext_filter_module);
562         f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
563     }
564     return f;
565 }
566
567 static apr_status_t init_filter_instance(ap_filter_t *f)
568 {
569     ef_ctx_t *ctx;
570     ef_dir_t *dc;
571     apr_status_t rv;
572
573     f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
574     dc = ap_get_module_config(f->r->per_dir_config,
575                               &ext_filter_module);
576     ctx->dc = dc;
577     /* look for the user-defined filter */
578     ctx->filter = find_filter_def(f->r->server, f->frec->name);
579     if (!ctx->filter) {
580         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
581                       "couldn't find definition of filter '%s'",
582                       f->frec->name);
583         return APR_EINVAL;
584     }
585     ctx->p = f->r->pool;
586     if (ctx->filter->intype &&
587         ctx->filter->intype != INTYPE_ALL) {
588         const char *ctypes;
589
590         if (ctx->filter->mode == INPUT_FILTER) {
591             ctypes = apr_table_get(f->r->headers_in, "Content-Type");
592         }
593         else {
594             ctypes = f->r->content_type;
595         }
596
597         if (ctypes) {
598             const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
599
600             if (strcasecmp(ctx->filter->intype, ctype)) {
601                 /* wrong IMT for us; don't mess with the output */
602                 ctx->noop = 1;
603             }
604         }
605         else {
606             ctx->noop = 1;
607         }
608     }
609     if (ctx->filter->enable_env &&
610         !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
611         /* an environment variable that enables the filter isn't set; bail */
612         ctx->noop = 1;
613     }
614     if (ctx->filter->disable_env &&
615         apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) {
616         /* an environment variable that disables the filter is set; bail */
617         ctx->noop = 1;
618     }
619     if (!ctx->noop) {
620         rv = init_ext_filter_process(f);
621         if (rv != APR_SUCCESS) {
622             return rv;
623         }
624         if (ctx->filter->outtype &&
625             ctx->filter->outtype != OUTTYPE_UNCHANGED) {
626             ap_set_content_type(f->r, ctx->filter->outtype);
627         }
628         if (ctx->filter->preserves_content_length != 1) {
629             /* nasty, but needed to avoid confusing the browser
630              */
631             apr_table_unset(f->r->headers_out, "Content-Length");
632         }
633     }
634
635     if (dc->debug >= DBGLVL_SHOWOPTIONS) {
636         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
637                       "%sfiltering `%s' of type `%s' through `%s', cfg %s",
638                       ctx->noop ? "NOT " : "",
639                       f->r->uri ? f->r->uri : f->r->filename,
640                       f->r->content_type ? f->r->content_type : "(unspecified)",
641                       ctx->filter->command,
642                       get_cfg_string(dc, ctx->filter, f->r->pool));
643     }
644
645     return APR_SUCCESS;
646 }
647
648 /* drain_available_output():
649  *
650  * if any data is available from the filter, read it and append it
651  * to the the bucket brigade
652  */
653 static apr_status_t drain_available_output(ap_filter_t *f,
654                                            apr_bucket_brigade *bb)
655 {
656     request_rec *r = f->r;
657     conn_rec *c = r->connection;
658     ef_ctx_t *ctx = f->ctx;
659     ef_dir_t *dc = ctx->dc;
660     apr_size_t len;
661     char buf[4096];
662     apr_status_t rv;
663     apr_bucket *b;
664
665     while (1) {
666         len = sizeof(buf);
667         rv = apr_file_read(ctx->proc->out,
668                       buf,
669                       &len);
670         if ((rv && !APR_STATUS_IS_EAGAIN(rv)) ||
671             dc->debug >= DBGLVL_GORY) {
672             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
673                           "apr_file_read(child output), len %" APR_SIZE_T_FMT,
674                           !rv ? len : -1);
675         }
676         if (rv != APR_SUCCESS) {
677             return rv;
678         }
679         b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
680         APR_BRIGADE_INSERT_TAIL(bb, b);
681         return APR_SUCCESS;
682     }
683     /* we should never get here; if we do, a bogus error message would be
684      * the least of our problems
685      */
686     return APR_ANONYMOUS;
687 }
688
689 static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
690                                         apr_size_t len, apr_bucket_brigade *bb)
691 {
692     ef_ctx_t *ctx = f->ctx;
693     ef_dir_t *dc = ctx->dc;
694     apr_status_t rv;
695     apr_size_t bytes_written = 0;
696     apr_size_t tmplen;
697
698     do {
699         tmplen = len - bytes_written;
700         rv = apr_file_write(ctx->proc->in,
701                        (const char *)data + bytes_written,
702                        &tmplen);
703         bytes_written += tmplen;
704         if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
705             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
706                           "apr_file_write(child input), len %" APR_SIZE_T_FMT,
707                           tmplen);
708             return rv;
709         }
710         if (APR_STATUS_IS_EAGAIN(rv)) {
711             /* XXX handle blocking conditions here...  if we block, we need
712              * to read data from the child process and pass it down to the
713              * next filter!
714              */
715             rv = drain_available_output(f, bb);
716             if (APR_STATUS_IS_EAGAIN(rv)) {
717 #if APR_FILES_AS_SOCKETS
718                 int num_events;
719                 const apr_pollfd_t *pdesc;
720
721                 rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
722                                       &num_events, &pdesc);
723                 if (rv || dc->debug >= DBGLVL_GORY) {
724                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
725                                   rv, f->r, "apr_pollset_poll()");
726                 }
727                 if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
728                     /* some error such as APR_TIMEUP */
729                     return rv;
730                 }
731 #else /* APR_FILES_AS_SOCKETS */
732                 /* Yuck... I'd really like to wait until I can read
733                  * or write, but instead I have to sleep and try again
734                  */
735                 apr_sleep(100000); /* 100 milliseconds */
736                 if (dc->debug >= DBGLVL_GORY) {
737                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
738                                   0, f->r, "apr_sleep()");
739                 }
740 #endif /* APR_FILES_AS_SOCKETS */
741             }
742             else if (rv != APR_SUCCESS) {
743                 return rv;
744             }
745         }
746     } while (bytes_written < len);
747     return rv;
748 }
749
750 /* ef_unified_filter:
751  *
752  * runs the bucket brigade bb through the filter and puts the result into
753  * bb, dropping the previous content of bb (the input)
754  */
755
756 static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
757 {
758     request_rec *r = f->r;
759     conn_rec *c = r->connection;
760     ef_ctx_t *ctx = f->ctx;
761     apr_bucket *b;
762     ef_dir_t *dc;
763     apr_size_t len;
764     const char *data;
765     apr_status_t rv;
766     char buf[4096];
767     apr_bucket *eos = NULL;
768     apr_bucket_brigade *bb_tmp;
769
770     dc = ctx->dc;
771     bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
772
773     for (b = APR_BRIGADE_FIRST(bb);
774          b != APR_BRIGADE_SENTINEL(bb);
775          b = APR_BUCKET_NEXT(b))
776     {
777         if (APR_BUCKET_IS_EOS(b)) {
778             eos = b;
779             break;
780         }
781
782         rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
783         if (rv != APR_SUCCESS) {
784             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "apr_bucket_read()");
785             return rv;
786         }
787
788         /* Good cast, we just tested len isn't negative */
789         if (len > 0 &&
790             (rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
791                 != APR_SUCCESS) {
792             return rv;
793         }
794     }
795
796     apr_brigade_cleanup(bb);
797     APR_BRIGADE_CONCAT(bb, bb_tmp);
798     apr_brigade_destroy(bb_tmp);
799
800     if (eos) {
801         /* close the child's stdin to signal that no more data is coming;
802          * that will cause the child to finish generating output
803          */
804         if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
805             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
806                           "apr_file_close(child input)");
807             return rv;
808         }
809         /* since we've seen eos and closed the child's stdin, set the proper pipe
810          * timeout; we don't care if we don't return from apr_file_read() for a while...
811          */
812         rv = apr_file_pipe_timeout_set(ctx->proc->out,
813                                        r->server->timeout);
814         if (rv) {
815             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
816                           "apr_file_pipe_timeout_set(child output)");
817             return rv;
818         }
819     }
820
821     do {
822         len = sizeof(buf);
823         rv = apr_file_read(ctx->proc->out,
824                       buf,
825                       &len);
826         if ((rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv)) ||
827             dc->debug >= DBGLVL_GORY) {
828             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
829                           "apr_file_read(child output), len %" APR_SIZE_T_FMT,
830                           !rv ? len : -1);
831         }
832         if (APR_STATUS_IS_EAGAIN(rv)) {
833             if (eos) {
834                 /* should not occur, because we have an APR timeout in place */
835                 AP_DEBUG_ASSERT(1 != 1);
836             }
837             return APR_SUCCESS;
838         }
839
840         if (rv == APR_SUCCESS) {
841             b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
842             APR_BRIGADE_INSERT_TAIL(bb, b);
843         }
844     } while (rv == APR_SUCCESS);
845
846     if (!APR_STATUS_IS_EOF(rv)) {
847         return rv;
848     }
849
850     if (eos) {
851         b = apr_bucket_eos_create(c->bucket_alloc);
852         APR_BRIGADE_INSERT_TAIL(bb, b);
853     }
854
855     return APR_SUCCESS;
856 }
857
858 static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
859 {
860     request_rec *r = f->r;
861     ef_ctx_t *ctx = f->ctx;
862     apr_status_t rv;
863
864     if (!ctx) {
865         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
866             return rv;
867         }
868         ctx = f->ctx;
869     }
870     if (ctx->noop) {
871         ap_remove_output_filter(f);
872         return ap_pass_brigade(f->next, bb);
873     }
874
875     rv = ef_unified_filter(f, bb);
876     if (rv != APR_SUCCESS) {
877         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
878                       "ef_unified_filter() failed");
879     }
880
881     if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
882         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
883                       "ap_pass_brigade() failed");
884     }
885     return rv;
886 }
887
888 static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
889                            ap_input_mode_t mode, apr_read_type_e block,
890                            apr_off_t readbytes)
891 {
892     ef_ctx_t *ctx = f->ctx;
893     apr_status_t rv;
894
895     if (!ctx) {
896         if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
897             return rv;
898         }
899         ctx = f->ctx;
900     }
901
902     if (ctx->noop) {
903         ap_remove_input_filter(f);
904         return ap_get_brigade(f->next, bb, mode, block, readbytes);
905     }
906
907     rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
908     if (rv != APR_SUCCESS) {
909         return rv;
910     }
911
912     rv = ef_unified_filter(f, bb);
913     return rv;
914 }
915
916 module AP_MODULE_DECLARE_DATA ext_filter_module =
917 {
918     STANDARD20_MODULE_STUFF,
919     create_ef_dir_conf,
920     merge_ef_dir_conf,
921     create_ef_server_conf,
922     NULL,
923     cmds,
924     register_hooks
925 };