]> granicus.if.org Git - apache/blobdiff - modules/filters/mod_ext_filter.c
Backport:
[apache] / modules / filters / mod_ext_filter.c
index 31c773672dac0193260ebf598e203e7e47f40798..3412c21d38cbb1e088e9e18edadc89e13d1c0dff 100644 (file)
@@ -1,59 +1,17 @@
-/* ====================================================================
- * The Apache Software License, Version 1.1
+/* 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
  *
- * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
- * reserved.
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The end-user documentation included with the redistribution,
- *    if any, must include the following acknowledgment:
- *       "This product includes software developed by the
- *        Apache Software Foundation (http://www.apache.org/)."
- *    Alternately, this acknowledgment may appear in the software itself,
- *    if and wherever such third-party acknowledgments normally appear.
- *
- * 4. The names "Apache" and "Apache Software Foundation" must
- *    not be used to endorse or promote products derived from this
- *    software without prior written permission. For written
- *    permission, please contact apache@apache.org.
- *
- * 5. Products derived from this software may not be called "Apache",
- *    nor may "Apache" appear in their name, without prior written
- *    permission of the Apache Software Foundation.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
- * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
- * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- * Portions of this software are based upon public domain software
- * originally written at the National Center for Supercomputing Applications,
- * University of Illinois, Urbana-Champaign.
+ * 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.
  */
 
 /*
 #include "http_config.h"
 #include "http_log.h"
 #include "http_protocol.h"
-#define CORE_PRIVATE
+
 #include "http_core.h"
 #include "apr_buckets.h"
 #include "util_filter.h"
 #include "util_script.h"
+#include "util_time.h"
 #include "apr_strings.h"
 #include "apr_hash.h"
 #include "apr_lib.h"
@@ -97,8 +56,8 @@ typedef struct ef_filter_t {
 } ef_filter_t;
 
 typedef struct ef_dir_t {
-    int debug;
     int log_stderr;
+    int onfail;
 } ef_dir_t;
 
 typedef struct ef_ctx_t {
@@ -109,7 +68,7 @@ typedef struct ef_ctx_t {
     ef_filter_t *filter;
     int noop;
 #if APR_FILES_AS_SOCKETS
-    apr_pollfd_t *pollset;
+    apr_pollset_t *pollset;
 #endif
 } ef_ctx_t;
 
@@ -117,16 +76,18 @@ module AP_MODULE_DECLARE_DATA ext_filter_module;
 static const server_rec *main_server;
 
 static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
+static apr_status_t ef_input_filter(ap_filter_t *, apr_bucket_brigade *,
+                                    ap_input_mode_t, apr_read_type_e,
+                                    apr_off_t);
 
-#define DBGLVL_SHOWOPTIONS         1
-#define DBGLVL_GORY                9
+#define ERRFN_USERDATA_KEY         "EXTFILTCHILDERRFN"
 
 static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
 {
     ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
 
-    dc->debug = -1;
     dc->log_stderr = -1;
+    dc->onfail = -1;
 
     return dc;
 }
@@ -146,18 +107,18 @@ static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
     ef_dir_t *a = (ef_dir_t *)apr_pcalloc (p, sizeof(ef_dir_t));
     ef_dir_t *base = (ef_dir_t *)basev, *over = (ef_dir_t *)overridesv;
 
-    if (over->debug != -1) {        /* if admin coded something... */
-        a->debug = over->debug;
+    if (over->log_stderr != -1) {   /* if admin coded something... */
+        a->log_stderr = over->log_stderr;
     }
     else {
-        a->debug = base->debug;
+        a->log_stderr = base->log_stderr;
     }
 
-    if (over->log_stderr != -1) {   /* if admin coded something... */
-        a->log_stderr = over->log_stderr;
+    if (over->onfail != -1) {   /* if admin coded something... */
+        a->onfail = over->onfail;
     }
     else {
-        a->log_stderr = base->log_stderr;
+        a->onfail = base->onfail;
     }
 
     return a;
@@ -168,17 +129,20 @@ static const char *add_options(cmd_parms *cmd, void *in_dc,
 {
     ef_dir_t *dc = in_dc;
 
-    if (!strncasecmp(arg, "DebugLevel=", 11)) {
-        dc->debug = atoi(arg + 11);
-    }
-    else if (!strcasecmp(arg, "LogStderr")) {
+    if (!strcasecmp(arg, "LogStderr")) {
         dc->log_stderr = 1;
     }
     else if (!strcasecmp(arg, "NoLogStderr")) {
         dc->log_stderr = 0;
     }
+    else if (!strcasecmp(arg, "Onfail=remove")) {
+        dc->onfail = 1;
+    }
+    else if (!strcasecmp(arg, "Onfail=abort")) {
+        dc->onfail = 0;
+    }
     else {
-        return apr_pstrcat(cmd->temp_pool, 
+        return apr_pstrcat(cmd->temp_pool,
                            "Invalid ExtFilterOptions option: ",
                            arg,
                            NULL);
@@ -222,15 +186,16 @@ static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filt
     else
     {
         /* simple path */
-        /* Allocate space for one argv pointer and parse the args. */
-        filter->args = (char **)apr_palloc(p, sizeof(char *));
+        /* Allocate space for two argv pointers and parse the args. */
+        filter->args = (char **)apr_palloc(p, 2 * sizeof(char *));
         filter->args[0] = ap_getword_white(p, args);
+        filter->args[1] = NULL; /* end of args */
     }
     if (!filter->args[0]) {
         return "Invalid cmd= parameter";
     }
     filter->command = filter->args[0];
-    
+
     return NULL;
 }
 
@@ -240,6 +205,7 @@ static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
                                              &ext_filter_module);
     const char *token;
     const char *name;
+    char *normalized_name;
     ef_filter_t *filter;
 
     name = ap_getword_white(cmd->pool, &args);
@@ -247,7 +213,16 @@ static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
         return "Filter name not found";
     }
 
-    if (apr_hash_get(conf->h, name, APR_HASH_KEY_STRING)) {
+    /* During request processing, we find information about the filter
+     * by looking up the filter name provided by core server in our
+     * hash table.  But the core server has normalized the filter
+     * name by converting it to lower case.  Thus, when adding the
+     * filter to our hash table we have to use lower case as well.
+     */
+    normalized_name = apr_pstrdup(cmd->pool, name);
+    ap_str_tolower(normalized_name);
+
+    if (apr_hash_get(conf->h, normalized_name, APR_HASH_KEY_STRING)) {
         return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
                             name);
     }
@@ -256,7 +231,7 @@ static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
     filter->name = name;
     filter->mode = OUTPUT_FILTER;
     filter->ftype = AP_FTYPE_RESOURCE;
-    apr_hash_set(conf->h, name, APR_HASH_KEY_STRING, filter);
+    apr_hash_set(conf->h, normalized_name, APR_HASH_KEY_STRING, filter);
 
     while (*args) {
         while (apr_isspace(*args)) {
@@ -273,7 +248,7 @@ static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
                 filter->preserves_content_length = 1;
             }
             else {
-                return apr_psprintf(cmd->pool, 
+                return apr_psprintf(cmd->pool,
                                     "mangled argument `%s'",
                                     token);
             }
@@ -309,14 +284,14 @@ static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
             filter->enable_env = token;
             continue;
         }
-        
+
         if (!strncasecmp(args, "disableenv=", 11)) {
             args += 11;
             token = ap_getword_white(cmd->pool, &args);
             filter->disable_env = token;
             continue;
         }
-        
+
         if (!strncasecmp(args, "intype=", 7)) {
             args += 7;
             filter->intype = ap_getword_white(cmd->pool, &args);
@@ -341,18 +316,16 @@ static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
                             args);
     }
 
-    /* parsing is done...  register the filter 
+    /* parsing is done...  register the filter
      */
     if (filter->mode == OUTPUT_FILTER) {
         /* XXX need a way to ensure uniqueness among all filters */
         ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
     }
-#if 0              /* no input filters yet */
     else if (filter->mode == INPUT_FILTER) {
         /* XXX need a way to ensure uniqueness among all filters */
-        ap_register_input_filter(filter->name, ef_input_filter, NULL, AP_FTYPE_RESOURCE);
+        ap_register_input_filter(filter->name, ef_input_filter, NULL, filter->ftype);
     }
-#endif
     else {
         ap_assert(1 != 1); /* we set the field wrong somehow */
     }
@@ -366,7 +339,7 @@ static const command_rec cmds[] =
                     add_options,
                     NULL,
                     ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
-                    "valid options: DebugLevel=n, LogStderr, NoLogStderr"),
+                    "valid options: LogStderr, NoLogStderr"),
     AP_INIT_RAW_ARGS("ExtFilterDefine",
                      define_filter,
                      NULL,
@@ -386,14 +359,13 @@ static void register_hooks(apr_pool_t *p)
     ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
 }
 
-static apr_status_t set_resource_limits(request_rec *r, 
+static apr_status_t set_resource_limits(request_rec *r,
                                         apr_procattr_t *procattr)
 {
 #if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
     defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
-    core_dir_config *conf = 
-        (core_dir_config *)ap_get_module_config(r->per_dir_config,
-                                                &core_module);
+    core_dir_config *conf =
+        (core_dir_config *)ap_get_core_module_config(r->per_dir_config);
     apr_status_t rv;
 
 #ifdef RLIMIT_CPU
@@ -419,6 +391,27 @@ static apr_status_t ef_close_file(void *vfile)
     return apr_file_close(vfile);
 }
 
+static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
+{
+    request_rec *r;
+    void *vr;
+    apr_file_t *stderr_log;
+    char errbuf[200];
+    char time_str[APR_CTIME_LEN];
+
+    apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
+    r = vr;
+    apr_file_open_stderr(&stderr_log, pool);
+    ap_recent_ctime(time_str, apr_time_now());
+    apr_file_printf(stderr_log,
+                    "[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
+                    time_str,
+                    r->useragent_ip,
+                    err,
+                    apr_strerror(err, errbuf, sizeof(errbuf)),
+                    description);
+}
+
 /* init_ext_filter_process: get the external filter process going
  * This is per-filter-instance (i.e., per-request) initialization.
  */
@@ -450,10 +443,20 @@ static apr_status_t init_ext_filter_process(ap_filter_t *f)
         ap_assert(rc == APR_SUCCESS);
     }
 
+    rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
+    ap_assert(rc == APR_SUCCESS);
+    apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
+
+    rc = apr_procattr_error_check_set(ctx->procattr, 1);
+    if (rc != APR_SUCCESS) {
+        return rc;
+    }
+
     /* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
      * and QUERY_STRING_UNESCAPED
      */
     ap_add_cgi_vars(f->r);
+    ap_add_common_vars(f->r);
     apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
     apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
     if (f->r->args) {
@@ -467,14 +470,14 @@ static apr_status_t init_ext_filter_process(ap_filter_t *f)
     env = (const char * const *) ap_create_environment(ctx->p,
                                                        f->r->subprocess_env);
 
-    rc = apr_proc_create(ctx->proc, 
-                            ctx->filter->command, 
-                            (const char * const *)ctx->filter->args, 
+    rc = apr_proc_create(ctx->proc,
+                            ctx->filter->command,
+                            (const char * const *)ctx->filter->args,
                             env, /* environment */
-                            ctx->procattr, 
+                            ctx->procattr,
                             ctx->p);
     if (rc != APR_SUCCESS) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r, APLOGNO(01458)
                       "couldn't create child process to run `%s'",
                       ctx->filter->command);
         return rc;
@@ -488,23 +491,27 @@ static apr_status_t init_ext_filter_process(ap_filter_t *f)
      * be open.
      */
 
-    apr_pool_cleanup_register(ctx->p, ctx->proc->in, 
+    apr_pool_cleanup_register(ctx->p, ctx->proc->in,
                          apr_pool_cleanup_null, /* other mechanism */
                          ef_close_file);
 
 #if APR_FILES_AS_SOCKETS
     {
-        apr_socket_t *newsock;
+        apr_pollfd_t pfd = { 0 };
 
-        rc = apr_poll_setup(&ctx->pollset, 2, ctx->p);
-        ap_assert(rc == APR_SUCCESS);
-        rc = apr_socket_from_file(&newsock, ctx->proc->in);
-        ap_assert(rc == APR_SUCCESS);
-        rc = apr_poll_socket_add(ctx->pollset, newsock, APR_POLLOUT);
+        rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0);
         ap_assert(rc == APR_SUCCESS);
-        rc = apr_socket_from_file(&newsock, ctx->proc->out);
+
+        pfd.p         = ctx->p;
+        pfd.desc_type = APR_POLL_FILE;
+        pfd.reqevents = APR_POLLOUT;
+        pfd.desc.f    = ctx->proc->in;
+        rc = apr_pollset_add(ctx->pollset, &pfd);
         ap_assert(rc == APR_SUCCESS);
-        rc = apr_poll_socket_add(ctx->pollset, newsock, APR_POLLIN);
+
+        pfd.reqevents = APR_POLLIN;
+        pfd.desc.f    = ctx->proc->out;
+        rc = apr_pollset_add(ctx->pollset, &pfd);
         ap_assert(rc == APR_SUCCESS);
     }
 #endif
@@ -514,8 +521,6 @@ static apr_status_t init_ext_filter_process(ap_filter_t *f)
 
 static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
 {
-    const char *debug_str = dc->debug == -1 ? 
-        "DebugLevel=0" : apr_psprintf(p, "DebugLevel=%d", dc->debug);
     const char *log_stderr_str = dc->log_stderr < 1 ?
         "NoLogStderr" : "LogStderr";
     const char *preserve_content_length_str = filter->preserves_content_length ?
@@ -524,11 +529,11 @@ static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t
         "*/*" : filter->intype;
     const char *outtype_str = !filter->outtype ?
         "(unchanged)" : filter->outtype;
-    
+
     return apr_psprintf(p,
-                        "ExtFilterOptions %s %s %s ExtFilterInType %s "
+                        "ExtFilterOptions %s %s ExtFilterInType %s "
                         "ExtFilterOuttype %s",
-                        debug_str, log_stderr_str, preserve_content_length_str,
+                        log_stderr_str, preserve_content_length_str,
                         intype_str, outtype_str);
 }
 
@@ -560,7 +565,7 @@ static apr_status_t init_filter_instance(ap_filter_t *f)
     /* look for the user-defined filter */
     ctx->filter = find_filter_def(f->r->server, f->frec->name);
     if (!ctx->filter) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01459)
                       "couldn't find definition of filter '%s'",
                       f->frec->name);
         return APR_EINVAL;
@@ -568,11 +573,16 @@ static apr_status_t init_filter_instance(ap_filter_t *f)
     ctx->p = f->r->pool;
     if (ctx->filter->intype &&
         ctx->filter->intype != INTYPE_ALL) {
-        if (!f->r->content_type) {
-            ctx->noop = 1;
+        const char *ctypes;
+
+        if (ctx->filter->mode == INPUT_FILTER) {
+            ctypes = apr_table_get(f->r->headers_in, "Content-Type");
         }
         else {
-            const char *ctypes = f->r->content_type;
+            ctypes = f->r->content_type;
+        }
+
+        if (ctypes) {
             const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
 
             if (strcasecmp(ctx->filter->intype, ctype)) {
@@ -580,6 +590,9 @@ static apr_status_t init_filter_instance(ap_filter_t *f)
                 ctx->noop = 1;
             }
         }
+        else {
+            ctx->noop = 1;
+        }
     }
     if (ctx->filter->enable_env &&
         !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
@@ -601,14 +614,14 @@ static apr_status_t init_filter_instance(ap_filter_t *f)
             ap_set_content_type(f->r, ctx->filter->outtype);
         }
         if (ctx->filter->preserves_content_length != 1) {
-            /* nasty, but needed to avoid confusing the browser 
+            /* nasty, but needed to avoid confusing the browser
              */
             apr_table_unset(f->r->headers_out, "Content-Length");
         }
     }
 
-    if (dc->debug >= DBGLVL_SHOWOPTIONS) {
-        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
+    if (APLOGrtrace1(f->r)) {
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, f->r,
                       "%sfiltering `%s' of type `%s' through `%s', cfg %s",
                       ctx->noop ? "NOT " : "",
                       f->r->uri ? f->r->uri : f->r->filename,
@@ -620,61 +633,52 @@ static apr_status_t init_filter_instance(ap_filter_t *f)
     return APR_SUCCESS;
 }
 
-/* drain_available_output(): 
+/* drain_available_output():
  *
- * if any data is available from the filter, read it and pass it
- * to the next filter
+ * if any data is available from the filter, read it and append it
+ * to the the bucket brigade
  */
-static apr_status_t drain_available_output(ap_filter_t *f)
+static apr_status_t drain_available_output(ap_filter_t *f,
+                                           apr_bucket_brigade *bb)
 {
     request_rec *r = f->r;
     conn_rec *c = r->connection;
     ef_ctx_t *ctx = f->ctx;
-    ef_dir_t *dc = ctx->dc;
     apr_size_t len;
     char buf[4096];
     apr_status_t rv;
-    apr_bucket_brigade *bb;
     apr_bucket *b;
 
     while (1) {
+        int lvl = APLOG_TRACE5;
         len = sizeof(buf);
-        rv = apr_file_read(ctx->proc->out,
-                      buf,
-                      &len);
-        if ((rv && !APR_STATUS_IS_EAGAIN(rv)) ||
-            dc->debug >= DBGLVL_GORY) {
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
-                          "apr_file_read(child output), len %" APR_SIZE_T_FMT,
-                          !rv ? len : -1);
-        }
+        rv = apr_file_read(ctx->proc->out, buf, &len);
+        if (rv && !APR_STATUS_IS_EAGAIN(rv))
+           lvl = APLOG_DEBUG;
+        ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01460)
+                      "apr_file_read(child output), len %" APR_SIZE_T_FMT,
+                      !rv ? len : -1);
         if (rv != APR_SUCCESS) {
             return rv;
         }
-        bb = apr_brigade_create(r->pool, c->bucket_alloc);
-        b = apr_bucket_transient_create(buf, len, c->bucket_alloc);
+        b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
         APR_BRIGADE_INSERT_TAIL(bb, b);
-        if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
-                          "ap_pass_brigade()");
-            return rv;
-        }
+        return APR_SUCCESS;
     }
     /* we should never get here; if we do, a bogus error message would be
-     * the least of our problems 
+     * the least of our problems
      */
     return APR_ANONYMOUS;
 }
 
-static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data, 
-                                        apr_size_t len)
+static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
+                                        apr_size_t len, apr_bucket_brigade *bb)
 {
     ef_ctx_t *ctx = f->ctx;
-    ef_dir_t *dc = ctx->dc;
     apr_status_t rv;
     apr_size_t bytes_written = 0;
     apr_size_t tmplen;
-    
+
     do {
         tmplen = len - bytes_written;
         rv = apr_file_write(ctx->proc->in,
@@ -682,40 +686,38 @@ static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
                        &tmplen);
         bytes_written += tmplen;
         if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01461)
                           "apr_file_write(child input), len %" APR_SIZE_T_FMT,
                           tmplen);
             return rv;
         }
         if (APR_STATUS_IS_EAGAIN(rv)) {
-            /* XXX handle blocking conditions here...  if we block, we need 
+            /* XXX handle blocking conditions here...  if we block, we need
              * to read data from the child process and pass it down to the
              * next filter!
              */
-            rv = drain_available_output(f);
+            rv = drain_available_output(f, bb);
             if (APR_STATUS_IS_EAGAIN(rv)) {
 #if APR_FILES_AS_SOCKETS
                 int num_events;
-                
-                rv = apr_poll(ctx->pollset, 2,
-                              &num_events, f->r->server->timeout);
-                if (rv || dc->debug >= DBGLVL_GORY) {
-                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
-                                  rv, f->r, "apr_poll()");
-                }
-                if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) { 
+                const apr_pollfd_t *pdesc;
+
+                rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
+                                      &num_events, &pdesc);
+                if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
+                    ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, f->r, APLOGNO(01462)
+                                  "apr_pollset_poll()");
                     /* some error such as APR_TIMEUP */
                     return rv;
                 }
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE6, rv, f->r,
+                              "apr_pollset_poll()");
 #else /* APR_FILES_AS_SOCKETS */
                 /* Yuck... I'd really like to wait until I can read
-                 * or write, but instead I have to sleep and try again 
+                 * or write, but instead I have to sleep and try again
                  */
                 apr_sleep(100000); /* 100 milliseconds */
-                if (dc->debug >= DBGLVL_GORY) {
-                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 
-                                  0, f->r, "apr_sleep()");
-                }
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r, "apr_sleep()");
 #endif /* APR_FILES_AS_SOCKETS */
             }
             else if (rv != APR_SUCCESS) {
@@ -726,33 +728,31 @@ static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
     return rv;
 }
 
-static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+/* ef_unified_filter:
+ *
+ * runs the bucket brigade bb through the filter and puts the result into
+ * bb, dropping the previous content of bb (the input)
+ */
+
+static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
 {
     request_rec *r = f->r;
     conn_rec *c = r->connection;
     ef_ctx_t *ctx = f->ctx;
     apr_bucket *b;
-    ef_dir_t *dc;
     apr_size_t len;
     const char *data;
     apr_status_t rv;
     char buf[4096];
     apr_bucket *eos = NULL;
+    apr_bucket_brigade *bb_tmp;
 
-    if (!ctx) {
-        if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
-            return rv;
-        }
-        ctx = f->ctx;
-    }
-    if (ctx->noop) {
-        ap_remove_output_filter(f);
-        return ap_pass_brigade(f->next, bb);
-    }
-    dc = ctx->dc;
-
-    APR_BRIGADE_FOREACH(b, bb) {
+    bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
 
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = APR_BUCKET_NEXT(b))
+    {
         if (APR_BUCKET_IS_EOS(b)) {
             eos = b;
             break;
@@ -760,55 +760,52 @@ static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
 
         rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
         if (rv != APR_SUCCESS) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "apr_bucket_read()");
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01463) "apr_bucket_read()");
             return rv;
         }
 
         /* Good cast, we just tested len isn't negative */
         if (len > 0 &&
-            (rv = pass_data_to_filter(f, data, (apr_size_t)len)) 
+            (rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
                 != APR_SUCCESS) {
             return rv;
         }
     }
 
-    apr_brigade_destroy(bb);
+    apr_brigade_cleanup(bb);
+    APR_BRIGADE_CONCAT(bb, bb_tmp);
+    apr_brigade_destroy(bb_tmp);
 
-    /* XXX What we *really* need to do once we've hit eos is create a pipe bucket
-     * from the child output pipe and pass down the pipe bucket + eos.
-     */
     if (eos) {
         /* close the child's stdin to signal that no more data is coming;
          * that will cause the child to finish generating output
          */
         if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01464)
                           "apr_file_close(child input)");
             return rv;
         }
-        /* since we've seen eos and closed the child's stdin, set the proper pipe 
-         * timeout; we don't care if we don't return from apr_file_read() for a while... 
+        /* since we've seen eos and closed the child's stdin, set the proper pipe
+         * timeout; we don't care if we don't return from apr_file_read() for a while...
          */
-        rv = apr_file_pipe_timeout_set(ctx->proc->out, 
+        rv = apr_file_pipe_timeout_set(ctx->proc->out,
                                        r->server->timeout);
         if (rv) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01465)
                           "apr_file_pipe_timeout_set(child output)");
             return rv;
         }
     }
 
     do {
+        int lvl = APLOG_TRACE6;
         len = sizeof(buf);
-        rv = apr_file_read(ctx->proc->out,
-                      buf,
-                      &len);
-        if ((rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv)) ||
-            dc->debug >= DBGLVL_GORY) {
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
-                          "apr_file_read(child output), len %" APR_SIZE_T_FMT,
-                          !rv ? len : -1);
-        }
+        rv = apr_file_read(ctx->proc->out, buf, &len);
+        if (rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv))
+            lvl = APLOG_ERR;
+        ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01466)
+                      "apr_file_read(child output), len %" APR_SIZE_T_FMT,
+                      !rv ? len : -1);
         if (APR_STATUS_IS_EAGAIN(rv)) {
             if (eos) {
                 /* should not occur, because we have an APR timeout in place */
@@ -816,16 +813,10 @@ static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
             }
             return APR_SUCCESS;
         }
-        
+
         if (rv == APR_SUCCESS) {
-            bb = apr_brigade_create(r->pool, c->bucket_alloc);
-            b = apr_bucket_transient_create(buf, len, c->bucket_alloc);
+            b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
             APR_BRIGADE_INSERT_TAIL(bb, b);
-            if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
-                              "ap_pass_brigade(filtered buffer) failed");
-                return rv;
-            }
         }
     } while (rv == APR_SUCCESS);
 
@@ -834,57 +825,106 @@ static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
     }
 
     if (eos) {
-        /* pass down eos */
-        bb = apr_brigade_create(r->pool, c->bucket_alloc);
         b = apr_bucket_eos_create(c->bucket_alloc);
         APR_BRIGADE_INSERT_TAIL(bb, b);
-        if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
-            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
-                          "ap_pass_brigade(eos) failed");
-            return rv;
-        }
     }
 
     return APR_SUCCESS;
 }
 
-#if 0
-static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb, 
-                           ap_input_mode_t mode, apr_read_type_e block,
-                           apr_off_t readbytes)
+static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
 {
+    request_rec *r = f->r;
+    ef_ctx_t *ctx = f->ctx;
     apr_status_t rv;
-    apr_bucket *b;
-    char *buf;
-    apr_ssize_t len;
-    char *zero;
 
-    rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
+    if (!ctx) {
+        if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
+            ctx = f->ctx;
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01467)
+                          "can't initialise output filter %s: %s",
+                          f->frec->name,
+                          (ctx->dc->onfail == 1) ? "removing" : "aborting");
+            ap_remove_output_filter(f);
+            if (ctx->dc->onfail == 1) {
+                return ap_pass_brigade(f->next, bb);
+            }
+            else {
+                apr_bucket *e;
+                f->r->status_line = "500 Internal Server Error";
+
+                apr_brigade_cleanup(bb);
+                e = ap_bucket_error_create(HTTP_INTERNAL_SERVER_ERROR,
+                                           NULL, r->pool,
+                                           f->c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(bb, e);
+                e = apr_bucket_eos_create(f->c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(bb, e);
+                ap_pass_brigade(f->next, bb);
+                return AP_FILTER_ERROR;
+            }
+        }
+        ctx = f->ctx;
+    }
+    if (ctx->noop) {
+        ap_remove_output_filter(f);
+        return ap_pass_brigade(f->next, bb);
+    }
+
+    rv = ef_unified_filter(f, bb);
     if (rv != APR_SUCCESS) {
-        return rv;
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01468)
+                      "ef_unified_filter() failed");
     }
 
-    APR_BRIGADE_FOREACH(b, bb) {
-        if (!APR_BUCKET_IS_EOS(b)) {
-            if ((rv = apr_bucket_read(b, (const char **)&buf, &len, APR_BLOCK_READ)) != APR_SUCCESS) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, "apr_bucket_read() failed");
-                return rv;
+    if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(01469)
+                      "ap_pass_brigade() failed");
+    }
+    return rv;
+}
+
+static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
+                           ap_input_mode_t mode, apr_read_type_e block,
+                           apr_off_t readbytes)
+{
+    ef_ctx_t *ctx = f->ctx;
+    apr_status_t rv;
+
+    if (!ctx) {
+        if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
+            ctx = f->ctx;
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01470)
+                          "can't initialise input filter %s: %s",
+                          f->frec->name,
+                          (ctx->dc->onfail == 1) ? "removing" : "aborting");
+            ap_remove_input_filter(f);
+            if (ctx->dc->onfail == 1) {
+                return ap_get_brigade(f->next, bb, mode, block, readbytes);
             }
-            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "apr_bucket_read -> %d bytes",
-                         len);
-            while ((zero = memchr(buf, '0', len))) {
-                *zero = 'a';
+            else {
+                f->r->status = HTTP_INTERNAL_SERVER_ERROR;
+                return HTTP_INTERNAL_SERVER_ERROR;
             }
         }
-        else
-            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "got eos bucket");
+        ctx = f->ctx;
     }
 
+    if (ctx->noop) {
+        ap_remove_input_filter(f);
+        return ap_get_brigade(f->next, bb, mode, block, readbytes);
+    }
+
+    rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = ef_unified_filter(f, bb);
     return rv;
 }
-#endif
 
-module AP_MODULE_DECLARE_DATA ext_filter_module =
+AP_DECLARE_MODULE(ext_filter) =
 {
     STANDARD20_MODULE_STUFF,
     create_ef_dir_conf,