From 19e74e6439992d256861b104e03fd3476111ee90 Mon Sep 17 00:00:00 2001 From: Jeff Trawick Date: Sat, 28 Oct 2000 15:17:41 +0000 Subject: [PATCH] initial commit of mod_ext_filter code and documentation git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@86760 13f79535-47bb-0310-9956-ffa450edef68 --- docs/manual/mod/directives.html | 2 + docs/manual/mod/index-bytype.html | 2 + docs/manual/mod/index.html | 2 + docs/manual/mod/mod_ext_filter.html | 286 ++++++++++ modules/experimental/mod_ext_filter.c | 790 ++++++++++++++++++++++++++ 5 files changed, 1082 insertions(+) create mode 100644 docs/manual/mod/mod_ext_filter.html create mode 100644 modules/experimental/mod_ext_filter.c diff --git a/docs/manual/mod/directives.html b/docs/manual/mod/directives.html index 76135a7a33..c133f35e5b 100644 --- a/docs/manual/mod/directives.html +++ b/docs/manual/mod/directives.html @@ -112,6 +112,8 @@ of the terms used in their descriptions available.
  • ExpiresByType
  • ExpiresDefault
  • ExtendedStatus +
  • ExtFilterDefine +
  • ExtFilterOptions
  • FancyIndexing
  • <Files>
  • <FilesMatch> diff --git a/docs/manual/mod/index-bytype.html b/docs/manual/mod/index-bytype.html index 5367a81aee..6a427d49bf 100644 --- a/docs/manual/mod/index-bytype.html +++ b/docs/manual/mod/index-bytype.html @@ -123,6 +123,8 @@ directives.
    Executing CGI scripts based on media type or request method.
    mod_isapi
    Windows ISAPI Extension support +
    mod_ext_filter +
    Filtering content with external programs.

    Internal Content Handlers

    diff --git a/docs/manual/mod/index.html b/docs/manual/mod/index.html index f3596d2c0a..2e831bdac9 100644 --- a/docs/manual/mod/index.html +++ b/docs/manual/mod/index.html @@ -66,6 +66,8 @@ directives.
    Demonstrates Apache API
    mod_expires
    Apply Expires: headers to resources +
    mod_ext_filter +
    Filtering output with external programs.
    mod_file_cache
    Caching files in memory for faster serving.
    mod_headers diff --git a/docs/manual/mod/mod_ext_filter.html b/docs/manual/mod/mod_ext_filter.html new file mode 100644 index 0000000000..0886782352 --- /dev/null +++ b/docs/manual/mod/mod_ext_filter.html @@ -0,0 +1,286 @@ + + + + Apache module mod_ext_filter + + + + +

    Module mod_ext_filter

    + +

    + This module is contained in the mod_ext_filter.c 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. + mod_ext_filter is not compiled into the server by default. +

    + +

    Summary

    +

    + This is an experimental module and should be used with + care. Test your mod_ext_filter configuration carefully to + ensure that it performs the desired function. You may wish to review + XXX for background on the Apache filtering model. +

    + +

    + mod_ext_filter 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: +

    + +
      +
    • the programming model is much simpler +
    • any programming/scripting language can be used, provided that + it allows the program to read from standard input and write to standard + output +
    • existing programs can be used unmodified as Apache filters +
    + +

    + Even when the performance characteristics are not suitable for production + use, mod_ext_filter can be used as a prototype environment + for filters. +

    + +

    Directives

    + + +
    + +

    ExtFilterDefine

    +

    + Syntax: ExtFilterDefine filtername parameters +
    + Default: None +
    + Context: server +
    + Override: none +
    + Status: Experimental +
    + Module: mod_ext_filter +
    + Compatibility: Only available in Apache 2.0 or later + +

    + The ExtFilterDefine directive defines the characteristics of + an external filter, including the program to run and its arguments. +

    + +

    + filtername 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. 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. +

    + +

    + Subsequent parameters can appear in any order and define the external command + to run and certain other characteristics. The only required parameter is + cmd=. These parameters are: +

    + +
    +
    cmd=cmdline +
    + The cmd= 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. +
    mode=mode +
    + mode should be output for now (the default). In the + future, mode=input will be used to specify a filter for request + bodies. +
    intype=imt +
    + This parameter specifies the internet media + type (i.e., MIME type) of documents which should be filtered. By default, + all documents are filtered. If intype= is specified, + the filter will be disabled for documents of other types. +
    outtype=imt +
    + 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. +
    PreservesContentLength +
    + The PreservesContentLength 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. +
    + +

    ExtFilterOptions

    +

    + Syntax: ExtFilterOptions option option ... +
    + Default: DebugLevel=0 NoLogStderr +
    + Context: directory +
    + Override: none +
    + Status: Experimental +
    + Module: mod_ext_filter +
    + Compatibility: Only available in Apache 2.0 or later + +

    + The ExtFilterOptions directive specifies special processing + options for mod_ext_filter. Option can be one of +

    +
    DebugLevel=n +
    + The DebugLevel keyword allows you to specify the level of + debug messages generated by mod_ext_filter. By default, no + debug messages are generated. This is equivalent to + DebugLevel=0. 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 mod_ext_filter.c. +

    + Note: The core directive LogLevel should be used to cause debug messages + to be stored in the Apache error log. +

    LogStderr | NoLogStderr +
    + The LogStderr keyword specifies that messages written to + standard error by the external filter program will be saved in the Apache + error log. NoLogStderr disables this feature. +
    +

    + + Example: + +
    +    ExtFilterOptions  LogStderr DebugLevel=0
    +  
    + + Messages written to the filter's standard error will be stored in the Apache + error log. No debug messages will be generated by + mod_ext_filter. + +

    + +

    Examples

    + +

    Generating HTML from some other type of response

    + +
    +    # 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>
    +  
    + +

    Implementing a content encoding filter

    + +
    +  # 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>
    +  
    + +

    Slowing down the server

    +
    +  # 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>
    +  
    + + + + diff --git a/modules/experimental/mod_ext_filter.c b/modules/experimental/mod_ext_filter.c new file mode 100644 index 0000000000..83490c44df --- /dev/null +++ b/modules/experimental/mod_ext_filter.c @@ -0,0 +1,790 @@ +/* ==================================================================== + * 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 + * . + * + * 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, +}; -- 2.40.0