--- /dev/null
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+ <HEAD>
+ <TITLE>Apache module mod_ext_filter</TITLE>
+ </HEAD>
+<!-- Background white, links blue (unvisited), navy (visited), red (active) -->
+ <BODY
+ BGCOLOR="#FFFFFF"
+ TEXT="#000000"
+ LINK="#0000FF"
+ VLINK="#000080"
+ ALINK="#FF0000"
+ >
+<!--#include virtual="header.html" -->
+ <H1 ALIGN="CENTER">Module mod_ext_filter</H1>
+
+ <P>
+ This module is contained in the <CODE>mod_ext_filter.c</CODE> file, with
+ Apache 2.0 and later. It provides the ability to pass the response body
+ through an external program before delivering to the client.
+ <CODE>mod_ext_filter</CODE> is not compiled into the server by default.
+ </P>
+
+ <H2>Summary</H2>
+ <P>
+ This is an <STRONG>experimental</STRONG> module and should be used with
+ care. Test your <CODE>mod_ext_filter</CODE> configuration carefully to
+ ensure that it performs the desired function. You may wish to review
+ XXX for background on the Apache filtering model.
+ </P>
+
+ <P>
+ <CODE>mod_ext_filter</CODE> presents a simple and familiar programming
+ model for filters. With this module, a program which reads from stdin and
+ writes to stdout (i.e., a Unix-style filter command) can be a filter for
+ Apache. This filtering mechanism is much slower than using a filter which
+ is specially written for the Apache API and runs inside of the Apache
+ server process, but it does have the following benefits:
+ </P>
+
+ <UL>
+ <LI>the programming model is much simpler
+ <LI>any programming/scripting language can be used, provided that
+ it allows the program to read from standard input and write to standard
+ output
+ <LI>existing programs can be used unmodified as Apache filters
+ </UL>
+
+ <P>
+ Even when the performance characteristics are not suitable for production
+ use, <CODE>mod_ext_filter</CODE> can be used as a prototype environment
+ for filters.
+ </P>
+
+ <H2>Directives</H2>
+ <UL>
+ <LI><A HREF="#extfilterdefine">ExtFilterDefine</A>
+ <LI><A HREF="#extfilteroptions">ExtFilterOptions</A>
+ </LI>
+ </UL>
+
+ <HR>
+
+ <H2><A NAME="extfilterdefine">ExtFilterDefine</A></H2>
+ <P>
+ <A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+ ><STRONG>Syntax:</STRONG></A> ExtFilterDefine <EM>filtername</EM> <EM>parameters</EM>
+ <BR>
+ <A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+ ><STRONG>Default:</STRONG></A> <EM>None</EM>
+ <BR>
+ <A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+ ><STRONG>Context:</STRONG></A> server
+ <BR>
+ <A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+ ><STRONG>Override:</STRONG></A> none
+ <BR>
+ <A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+ ><STRONG>Status:</STRONG></A> Experimental
+ <BR>
+ <A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+ ><STRONG>Module:</STRONG></A> mod_ext_filter
+ <BR>
+ <A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+ ><STRONG>Compatibility:</STRONG></A> Only available in Apache 2.0 or later
+
+ <P>
+ The <CODE>ExtFilterDefine</CODE> directive defines the characteristics of
+ an external filter, including the program to run and its arguments.
+ </P>
+
+ <P>
+ <EM>filtername</EM> specifies the name of the filter being defined. This name
+ can then be used in AddOutputFilter directives. It must be unique among all
+ registered filters. <EM>At the present time, no error is reported by the
+ register-filter API, so a problem with duplicate names isn't reported to the
+ user.</EM>
+ </P>
+
+ <P>
+ Subsequent parameters can appear in any order and define the external command
+ to run and certain other characteristics. The only required parameter is
+ <EM>cmd=</EM>. These parameters are:
+ </P>
+
+ <DL>
+ <DT>cmd=<EM>cmdline</EM>
+ <DD>
+ The <SAMP>cmd=</SAMP> keyword allows you to specify the external command
+ to run. If there are arguments after the program name, the command line
+ should be surrounded in quotation marks.
+ <DT>mode=<EM>mode</EM>
+ <DD>
+ <EM>mode</EM> should be <EM>output</EM> for now (the default). In the
+ future, <EM>mode=input</EM> will be used to specify a filter for request
+ bodies.
+ <DT>intype=<EM>imt</EM>
+ <DD>
+ This parameter specifies the internet media
+ type (i.e., MIME type) of documents which should be filtered. By default,
+ all documents are filtered. If <CODE>intype=</CODE> is specified,
+ the filter will be disabled for documents of other types.
+ <DT>outtype=<EM>imt</EM>
+ <DD>
+ This parameter specifies the internet media
+ type (i.e., MIME type) of filtered documents. It is useful when the filter
+ changes the internet media type as part of the filtering operation. By
+ default, the internet media type is unchanged.
+ <DT>PreservesContentLength
+ <DD>
+ The <SAMP>PreservesContentLength</SAMP> keyword specifies that the filter
+ preserves the content length. This is not the default, as most filters
+ change the content length. In the event that the filter doesn't modify the
+ length, this keyword should be specified.
+ </DL>
+
+ <H2><A NAME="extfilteroptions">ExtFilterOptions</A></H2>
+ <P>
+ <A
+ HREF="directive-dict.html#Syntax"
+ REL="Help"
+ ><STRONG>Syntax:</STRONG></A> ExtFilterOptions <EM>option option ...</EM>
+ <BR>
+ <A
+ HREF="directive-dict.html#Default"
+ REL="Help"
+ ><STRONG>Default:</STRONG></A> <EM>DebugLevel=0</EM> <EM>NoLogStderr</EM>
+ <BR>
+ <A
+ HREF="directive-dict.html#Context"
+ REL="Help"
+ ><STRONG>Context:</STRONG></A> directory
+ <BR>
+ <A
+ HREF="directive-dict.html#Override"
+ REL="Help"
+ ><STRONG>Override:</STRONG></A> none
+ <BR>
+ <A
+ HREF="directive-dict.html#Status"
+ REL="Help"
+ ><STRONG>Status:</STRONG></A> Experimental
+ <BR>
+ <A
+ HREF="directive-dict.html#Module"
+ REL="Help"
+ ><STRONG>Module:</STRONG></A> mod_ext_filter
+ <BR>
+ <A
+ HREF="directive-dict.html#Compatibility"
+ REL="Help"
+ ><STRONG>Compatibility:</STRONG></A> Only available in Apache 2.0 or later
+
+ <P>
+ The <CODE>ExtFilterOptions</CODE> directive specifies special processing
+ options for <CODE>mod_ext_filter</CODE>. <EM>Option</EM> can be one of
+ <DL>
+ <DT>DebugLevel=<EM>n</EM>
+ <DD>
+ The <SAMP>DebugLevel</SAMP> keyword allows you to specify the level of
+ debug messages generated by <CODE>mod_ext_filter</CODE>. By default, no
+ debug messages are generated. This is equivalent to
+ <SAMP>DebugLevel=0</SAMP>. With higher numbers, more debug messages are
+ generated, and server performance will be degraded. The actual meanings
+ of the numeric values are described with the definitions of the DBGLVL_
+ constants near the beginning of <CODE>mod_ext_filter.c</CODE>.
+ <P>
+ Note: The core directive LogLevel should be used to cause debug messages
+ to be stored in the Apache error log.
+ <DT>LogStderr | NoLogStderr
+ <DD>
+ The <SAMP>LogStderr</SAMP> keyword specifies that messages written to
+ standard error by the external filter program will be saved in the Apache
+ error log. <SAMP>NoLogStderr</SAMP> disables this feature.
+ </DL>
+ </P>
+
+ Example:
+
+ <PRE>
+ ExtFilterOptions LogStderr DebugLevel=0
+ </PRE>
+
+ Messages written to the filter's standard error will be stored in the Apache
+ error log. No debug messages will be generated by
+ <CODE>mod_ext_filter</CODE>.
+
+ <P>
+
+ <H2>Examples</H2>
+
+ <H3>Generating HTML from some other type of response</H3>
+
+ <PRE>
+ # mod_ext_filter directive to define a filter to HTML-ize text/c files
+ # using the external program /usr/bin/enscript, with the type of the
+ # result set to text/html
+ ExtFilterDefine c-to-html mode=output intype=text/c outtype=text/html \
+ cmd="/usr/bin/enscript --color -W html -Ec -o - -"
+
+ <Directory "/export/home/trawick/apacheinst/htdocs/c">
+
+ # core directive to cause the new filter to be run on output
+ AddOutputFilter c-to-heml
+
+ # mod_mime directive to set the type of .c files to text/c
+ AddType text/c .c
+
+ # mod_ext_filter directive to set the debug level just high
+ # enough to see a log message per request showing the configuration
+ # in force
+ ExtFilterOptions DebugLevel=1
+
+ </Directory>
+ </PRE>
+
+ <H3>Implementing a content encoding filter</H3>
+
+ <PRE>
+ # mod_ext_filter directive to define the external filter
+ ExtFilterDefine gzip mode=output cmd=/bin/gzip
+
+ <Location /gzipped>
+
+ # core directive to cause the gzip filter to be run on output
+ AddOutputFilter gzip
+
+ # mod_header directive to add "Content-Encoding: gzip" header field
+ Header set Content-Encoding gzip
+
+ </Location>
+ </PRE>
+
+ <H3>Slowing down the server</H3>
+ <PRE>
+ # mod_ext_filter directive to define a filter which runs everything
+ # through cat; cat doesn't modify anything; it just introduces extra
+ # pathlength and consumes more resources
+ ExtFilterDefine slowdown mode=output cmd=/bin/cat preservescontentlength
+
+ <Location />
+
+ # core directive to cause the slowdown filter to be run several times on
+ # output
+ AddOutputFilter slowdown slowdown slowdown
+
+ </Location>
+ </PRE>
+
+<!--#include virtual="footer.html" -->
+ </BODY>
+</HTML>
--- /dev/null
+/* ====================================================================
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2000 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * 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.
+ */
+
+/*
+ * mod_ext_filter allows Unix-style filters to filter http content.
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_log.h"
+#define CORE_PRIVATE
+#include "http_core.h"
+#include "ap_buckets.h"
+#include "util_filter.h"
+#include "apr_strings.h"
+#include "apr_hash.h"
+
+typedef struct ef_server_t {
+ apr_pool_t *p;
+ apr_hash_t *h;
+} ef_server_t;
+
+typedef struct ef_filter_t {
+ const char *name;
+ enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
+ const char *command;
+ int numArgs;
+ char *args[30];
+ const char *intype; /* list of IMTs we process (well, just one for now) */
+#define INTYPE_ALL (char *)1
+ const char *outtype; /* IMT of filtered output */
+#define OUTTYPE_UNCHANGED (char *)1
+ int preserves_content_length;
+} ef_filter_t;
+
+typedef struct ef_dir_t {
+ int debug;
+ int log_stderr;
+} ef_dir_t;
+
+typedef struct ef_ctx_t {
+ apr_pool_t *p;
+ apr_proc_t *proc;
+ apr_procattr_t *procattr;
+ ef_dir_t *dc;
+ ef_filter_t *filter;
+ int noop;
+#if APR_FILES_AS_SOCKETS
+ apr_pollfd_t *pollset;
+#endif
+} ef_ctx_t;
+
+module ext_filter_module;
+
+static apr_status_t ef_output_filter(ap_filter_t *, ap_bucket_brigade *);
+
+#define DBGLVL_SHOWOPTIONS 1
+#define DBGLVL_GORY 9
+
+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;
+
+ return dc;
+}
+
+static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
+{
+ ef_server_t *conf;
+
+ conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
+ conf->p = p;
+ conf->h = apr_make_hash(conf->p);
+ return conf;
+}
+
+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;
+ }
+ else {
+ a->debug = base->debug;
+ }
+
+ if (over->log_stderr != -1) { /* if admin coded something... */
+ a->log_stderr = over->log_stderr;
+ }
+ else {
+ a->log_stderr = base->log_stderr;
+ }
+
+ return a;
+}
+
+static const char *add_options(cmd_parms *cmd, void *in_dc,
+ const char *arg)
+{
+ ef_dir_t *dc = in_dc;
+
+ if (!strncasecmp(arg, "DebugLevel=", 11)) {
+ dc->debug = atoi(arg + 11);
+ }
+ else if (!strcasecmp(arg, "LogStderr")) {
+ dc->log_stderr = 1;
+ }
+ else if (!strcasecmp(arg, "NoLogStderr")) {
+ dc->log_stderr = 0;
+ }
+ else {
+ return apr_pstrcat(cmd->temp_pool,
+ "Invalid ExtFilterOptions option: ",
+ arg,
+ NULL);
+ }
+
+ return NULL;
+}
+
+static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
+{
+ if (**args == '"') {
+ const char *start = *args + 1;
+ char *parms;
+
+ ++*args; /* move past leading " */
+ while (**args && **args != '"') {
+ ++*args;
+ }
+ if (**args != '"') {
+ return "Expected cmd= delimiter";
+ }
+ parms = apr_pstrndup(p, start, *args - start);
+ ++*args; /* move past trailing " */
+
+ /* parms now has the command-line to parse */
+ while (filter->numArgs < 30 &&
+ strlen(filter->args[filter->numArgs] = ap_getword_white_nc(p, &parms))) {
+ ++filter->numArgs;
+ }
+ if (filter->numArgs < 1) {
+ return "cmd= parse error";
+ }
+ filter->args[filter->numArgs] = NULL; /* we stored "" in the while() loop */
+ filter->command = filter->args[0];
+ }
+ else
+ {
+ /* simple path */
+ filter->args[0] = ap_getword_white(p, args);
+ if (!filter->args[0]) {
+ return "Invalid cmd= parameter";
+ }
+ filter->numArgs = 1;
+ filter->command = filter->args[0];
+ }
+ return NULL;
+}
+
+static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
+{
+ ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
+ &ext_filter_module);
+ const char *token;
+ const char *name;
+ ef_filter_t *filter;
+
+ name = ap_getword_white(cmd->pool, &args);
+ if (!name) {
+ return "Filter name not found";
+ }
+
+ if (apr_hash_get(conf->h, name, APR_HASH_KEY_STRING)) {
+ return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
+ name);
+ }
+
+ filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
+ filter->name = name;
+ filter->mode = OUTPUT_FILTER;
+ apr_hash_set(conf->h, name, APR_HASH_KEY_STRING, filter);
+
+ while (*args) {
+ while (apr_isspace(*args)) {
+ ++args;
+ }
+
+ /* Nasty parsing... I wish I could simply use ap_getword_white()
+ * here and then look at the token, but ap_getword_white() doesn't
+ * do the right thing when we have cmd="word word word"
+ */
+ if (!strncasecmp(args, "preservescontentlength", 22)) {
+ token = ap_getword_white(cmd->pool, &args);
+ if (!strcasecmp(token, "preservescontentlength")) {
+ filter->preserves_content_length = 1;
+ }
+ else {
+ return apr_psprintf(cmd->pool,
+ "mangled argument `%s'",
+ token);
+ }
+ continue;
+ }
+
+ if (!strncasecmp(args, "mode=", 5)) {
+ args += 5;
+ token = ap_getword_white(cmd->pool, &args);
+ if (!strcasecmp(token, "output")) {
+ filter->mode = OUTPUT_FILTER;
+ }
+ else if (!strcasecmp(token, "input")) {
+ filter->mode = INPUT_FILTER;
+ }
+ else {
+ return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
+ token);
+ }
+ continue;
+ }
+
+ if (!strncasecmp(args, "intype=", 7)) {
+ args += 7;
+ filter->intype = ap_getword_white(cmd->pool, &args);
+ continue;
+ }
+
+ if (!strncasecmp(args, "outtype=", 8)) {
+ args += 8;
+ filter->outtype = ap_getword_white(cmd->pool, &args);
+ continue;
+ }
+
+ if (!strncasecmp(args, "cmd=", 4)) {
+ args += 4;
+ if ((token = parse_cmd(cmd->pool, &args, filter))) {
+ return token;
+ }
+ continue;
+ }
+
+ return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
+ args);
+ }
+
+ /* 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, AP_FTYPE_CONTENT);
+ }
+#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, AP_FTYPE_CONTENT);
+ }
+#endif
+ else {
+ ap_assert(1 != 1); /* we set the field wrong somehow */
+ }
+
+ return NULL;
+}
+
+static const command_rec cmds[] =
+{
+ AP_INIT_ITERATE("ExtFilterOptions",
+ add_options,
+ NULL,
+ ACCESS_CONF, /* same as AddInputFilter/AddOutputFilter */
+ "valid options: DebugLevel=n, LogStderr, NoLogStderr"),
+ AP_INIT_RAW_ARGS("ExtFilterDefine",
+ define_filter,
+ NULL,
+ RSRC_CONF,
+ "Define an external filter"),
+ {NULL}
+};
+
+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);
+ apr_status_t rv;
+
+#ifdef RLIMIT_CPU
+ rv = apr_setprocattr_limit(procattr, APR_LIMIT_CPU, conf->limit_cpu);
+ ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
+#endif
+#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
+ rv = apr_setprocattr_limit(procattr, APR_LIMIT_MEM, conf->limit_mem);
+ ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
+#endif
+#ifdef RLIMIT_NPROC
+ rv = apr_setprocattr_limit(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
+ ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
+#endif
+
+#endif /* if at least one limit defined */
+
+ return APR_SUCCESS;
+}
+
+static apr_status_t ef_close_file(void *vfile)
+{
+ apr_file_t *f = vfile;
+
+ return apr_close(vfile);
+}
+
+/* init_ext_filter_process: get the external filter process going
+ * This is per-filter-instance (i.e., per-request) initialization.
+ */
+static apr_status_t init_ext_filter_process(ap_filter_t *f)
+{
+ ef_ctx_t *ctx = f->ctx;
+ apr_status_t rc;
+ ef_dir_t *dc = ctx->dc;
+
+ ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
+
+ rc = apr_createprocattr_init(&ctx->procattr, ctx->p);
+ ap_assert(rc == APR_SUCCESS);
+
+ rc = apr_setprocattr_io(ctx->procattr,
+ APR_CHILD_BLOCK,
+ APR_CHILD_BLOCK,
+ APR_CHILD_BLOCK);
+ ap_assert(rc == APR_SUCCESS);
+
+ rc = set_resource_limits(f->r, ctx->procattr);
+ ap_assert(rc == APR_SUCCESS);
+
+ if (dc->log_stderr > 0) {
+ rc = apr_setprocattr_childerr(ctx->procattr,
+ f->r->server->error_log, /* stderr in child */
+ NULL);
+ ap_assert(rc == APR_SUCCESS);
+ }
+
+ rc = apr_create_process(ctx->proc,
+ ctx->filter->command,
+ ctx->filter->args,
+ NULL, /* environment */
+ ctx->procattr,
+ ctx->p);
+ if (rc != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
+ "couldn't create child process to run `%s'",
+ ctx->filter->command);
+ return rc;
+ }
+
+ apr_note_subprocess(ctx->p, ctx->proc, kill_after_timeout);
+
+ /* We don't want the handle to the child's stdin inherited by any
+ * other processes created by httpd. Otherwise, when we close our
+ * handle, the child won't see EOF because another handle will still
+ * be open.
+ */
+
+ apr_register_cleanup(ctx->p, ctx->proc->in, NULL, ef_close_file);
+
+#if APR_FILES_AS_SOCKETS
+ {
+ apr_socket_t *newsock;
+
+ rc = apr_setup_poll(&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_add_poll_socket(ctx->pollset, newsock, APR_POLLOUT);
+ ap_assert(rc == APR_SUCCESS);
+ rc = apr_socket_from_file(&newsock, ctx->proc->out);
+ ap_assert(rc == APR_SUCCESS);
+ rc = apr_add_poll_socket(ctx->pollset, newsock, APR_POLLIN);
+ ap_assert(rc == APR_SUCCESS);
+ }
+#endif
+
+ return APR_SUCCESS;
+}
+
+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 ?
+ "PreservesContentLength" : "!PreserveContentLength";
+ const char *intype_str = !filter->intype ?
+ "*/*" : filter->intype;
+ const char *outtype_str = !filter->outtype ?
+ "(unchanged)" : filter->outtype;
+
+ return apr_psprintf(p,
+ "ExtFilterOptions %s %s %s ExtFilterInType %s "
+ "ExtFilterOuttype %s",
+ debug_str, log_stderr_str, preserve_content_length_str,
+ intype_str, outtype_str);
+}
+
+static apr_status_t init_filter_instance(ap_filter_t *f)
+{
+ ef_ctx_t *ctx;
+ ef_dir_t *dc;
+ ef_server_t *sc;
+ apr_status_t rv;
+
+ f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
+ dc = ap_get_module_config(f->r->per_dir_config,
+ &ext_filter_module);
+ sc = ap_get_module_config(f->r->server->module_config,
+ &ext_filter_module);
+ ctx->dc = dc;
+ /* look for the user-defined filter */
+ ctx->filter = apr_hash_get(sc->h, f->frec->name, APR_HASH_KEY_STRING);
+ if (!ctx->filter) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, f->r,
+ "couldn't find definition of filter '%s'",
+ f->frec->name);
+ return APR_EINVAL;
+ }
+ ctx->p = f->r->pool;
+ if (ctx->filter->intype &&
+ ctx->filter->intype != INTYPE_ALL &&
+ strcasecmp(ctx->filter->intype, f->r->content_type)) {
+ /* wrong IMT for us; don't mess with the output */
+ ctx->noop = 1;
+ }
+ else {
+ rv = init_ext_filter_process(f);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ if (ctx->filter->outtype &&
+ ctx->filter->outtype != OUTTYPE_UNCHANGED) {
+ f->r->content_type = ctx->filter->outtype;
+ }
+ if (ctx->filter->preserves_content_length != 1) {
+ /* 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|APLOG_NOERRNO, 0, f->r,
+ "%sfiltering `%s' through `%s', cfg %s",
+ ctx->noop ? "skipping: " : "",
+ f->r->uri ? f->r->uri : f->r->filename,
+ ctx->filter->command,
+ get_cfg_string(dc, ctx->filter, f->r->pool));
+ }
+
+ return APR_SUCCESS;
+}
+
+/* drain_available_output():
+ *
+ * if any data is available from the filter, read it and pass it
+ * to the next filter
+ */
+static apr_status_t drain_available_output(ap_filter_t *f)
+{
+ ef_ctx_t *ctx = f->ctx;
+ ef_dir_t *dc = ctx->dc;
+ apr_ssize_t len;
+ char buf[4096];
+ apr_status_t rv;
+ ap_bucket_brigade *bb;
+ ap_bucket *b;
+
+ while (1) {
+ len = sizeof(buf);
+ rv = apr_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, f->r,
+ "apr_read(child output), len %d",
+ !rv ? len : -1);
+ }
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ bb = ap_brigade_create(f->r->pool);
+ b = ap_bucket_create_transient(buf, len);
+ AP_BRIGADE_INSERT_TAIL(bb, b);
+ if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+ "ap_pass_brigade()");
+ return rv;
+ }
+ }
+ /* we should never get here; if we do, a bogus error message would be
+ * the least of our problems
+ */
+ return APR_ANONYMOUS;
+}
+
+static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
+ apr_ssize_t len)
+{
+ ef_ctx_t *ctx = f->ctx;
+ ef_dir_t *dc = ctx->dc;
+ apr_status_t rv;
+ apr_ssize_t bytes_written = 0;
+ apr_ssize_t tmplen;
+
+ do {
+ tmplen = len - bytes_written;
+ rv = apr_write(ctx->proc->in,
+ (const char *)data + bytes_written,
+ &tmplen);
+ bytes_written += tmplen;
+ if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+ "apr_write(child input), len %d",
+ tmplen);
+ return rv;
+ }
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ /* 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);
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+#if APR_FILES_AS_SOCKETS
+ int num_events;
+
+ rv = apr_poll(ctx->pollset,
+ &num_events,
+ f->r->server->timeout * APR_USEC_PER_SEC);
+ if (rv || dc->debug >= DBGLVL_GORY) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
+ rv, f->r, "apr_poll()");
+ }
+ if (rv != APR_SUCCESS && rv != APR_EINTR) {
+ /* some error such as APR_TIMEUP */
+ return rv;
+ }
+#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
+ */
+ apr_sleep(100000); /* 100 milliseconds */
+ if (dc->debug >= DBGLVL_GORY) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO,
+ 0, f->r, "apr_sleep()");
+ }
+#endif /* APR_FILES_AS_SOCKETS */
+ }
+ else if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+ } while (bytes_written < len);
+ return rv;
+}
+
+static apr_status_t ef_output_filter(ap_filter_t *f, ap_bucket_brigade *bb)
+{
+ ef_ctx_t *ctx = f->ctx;
+ ap_bucket *b;
+ ef_dir_t *dc;
+ apr_ssize_t len;
+ const char *data;
+ apr_status_t rv;
+ char buf[4096];
+ ap_bucket *eos = NULL;
+
+ 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;
+
+ AP_BRIGADE_FOREACH(b, bb) {
+
+ if (AP_BUCKET_IS_EOS(b)) {
+ eos = b;
+ break;
+ }
+
+ rv = ap_bucket_read(b, &data, &len, 1);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, "ap_bucket_read()");
+ return rv;
+ }
+
+ if (len > 0 &&
+ (rv = pass_data_to_filter(f, data, len)) != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ ap_brigade_destroy(bb);
+
+ /* 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_close(ctx->proc->in)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+ "apr_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_read() for a while...
+ */
+ rv = apr_set_pipe_timeout(ctx->proc->out,
+ f->r->server->timeout * APR_USEC_PER_SEC);
+ if (rv) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+ "apr_set_pipe_timeout(child output)");
+ return rv;
+ }
+ }
+
+ do {
+ len = sizeof(buf);
+ rv = apr_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, f->r,
+ "apr_read(child output), len %d",
+ !rv ? len : -1);
+ }
+ if (APR_STATUS_IS_EAGAIN(rv)) {
+ if (eos) {
+ /* should not occur, because we have an APR timeout in place */
+ AP_DEBUG_ASSERT(1 != 1);
+ }
+ return APR_SUCCESS;
+ }
+
+ if (rv == APR_SUCCESS) {
+ bb = ap_brigade_create(f->r->pool);
+ b = ap_bucket_create_transient(buf, len);
+ AP_BRIGADE_INSERT_TAIL(bb, b);
+ if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+ "ap_pass_brigade(filtered buffer) failed");
+ return rv;
+ }
+ }
+ } while (rv == APR_SUCCESS);
+
+ if (!APR_STATUS_IS_EOF(rv)) {
+ return rv;
+ }
+
+ if (eos) {
+ /* pass down eos */
+ bb = ap_brigade_create(f->r->pool);
+ b = ap_bucket_create_eos();
+ AP_BRIGADE_INSERT_TAIL(bb, b);
+ if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
+ "ap_pass_brigade(eos) failed");
+ return rv;
+ }
+ }
+
+ return APR_SUCCESS;
+}
+
+#if 0
+static int ef_input_filter(ap_filter_t *f, ap_bucket_brigade *bb,
+ ap_input_mode_t mode)
+{
+ apr_status_t rv;
+ ap_bucket *b;
+ char *buf;
+ apr_ssize_t len;
+ char *zero;
+
+ rv = ap_get_brigade(f->next, bb, mode);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+
+ AP_BRIGADE_FOREACH(b, bb) {
+ if (!AP_BUCKET_IS_EOS(b)) {
+ if ((rv = ap_bucket_read(b, (const char **)&buf, &len, 0)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, "ap_bucket_read() failed");
+ return rv;
+ }
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "ap_bucket_read -> %d bytes",
+ len);
+ while ((zero = memchr(buf, '0', len))) {
+ *zero = 'a';
+ }
+ }
+ else
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "got eos bucket");
+ }
+
+ return rv;
+}
+#endif
+
+module ext_filter_module =
+{
+ STANDARD20_MODULE_STUFF,
+ create_ef_dir_conf,
+ merge_ef_dir_conf,
+ create_ef_server_conf,
+ NULL,
+ cmds,
+};