diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
commit | fe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch) | |
tree | b80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules/filters/mod_ext_filter.c | |
parent | Initial commit. (diff) | |
download | apache2-fe39ffb8b90ae4e002ed73fe98617cd590abb467.tar.xz apache2-fe39ffb8b90ae4e002ed73fe98617cd590abb467.zip |
Adding upstream version 2.4.56.upstream/2.4.56upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/filters/mod_ext_filter.c')
-rw-r--r-- | modules/filters/mod_ext_filter.c | 954 |
1 files changed, 954 insertions, 0 deletions
diff --git a/modules/filters/mod_ext_filter.c b/modules/filters/mod_ext_filter.c new file mode 100644 index 0000000..7afd8dd --- /dev/null +++ b/modules/filters/mod_ext_filter.c @@ -0,0 +1,954 @@ +/* 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +/* + * mod_ext_filter allows Unix-style filters to filter http content. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" +#include "http_protocol.h" + +#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" +#include "apr_poll.h" +#define APR_WANT_STRFUNC +#include "apr_want.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; + ap_filter_type ftype; + const char *command; + const char *enable_env; + const char *disable_env; + char **args; + 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 log_stderr; + int onfail; +} 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, hit_eos; +#if APR_FILES_AS_SOCKETS + apr_pollset_t *pollset; +#endif +} ef_ctx_t; + +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 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->log_stderr = -1; + dc->onfail = -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_hash_make(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->log_stderr != -1) { /* if admin coded something... */ + a->log_stderr = over->log_stderr; + } + else { + a->log_stderr = base->log_stderr; + } + + if (over->onfail != -1) { /* if admin coded something... */ + a->onfail = over->onfail; + } + else { + a->onfail = base->onfail; + } + + return a; +} + +static const char *add_options(cmd_parms *cmd, void *in_dc, + const char *arg) +{ + ef_dir_t *dc = in_dc; + + 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, + "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; + int escaping = 0; + apr_status_t rv; + + ++*args; /* move past leading " */ + /* find true end of args string (accounting for escaped quotes) */ + while (**args && (**args != '"' || (**args == '"' && escaping))) { + if (escaping) { + escaping = 0; + } + else if (**args == '\\') { + escaping = 1; + } + ++*args; + } + if (**args != '"') { + return "Expected cmd= delimiter"; + } + /* copy *just* the arg string for parsing, */ + parms = apr_pstrndup(p, start, *args - start); + ++*args; /* move past trailing " */ + + /* parse and tokenize the args. */ + rv = apr_tokenize_to_argv(parms, &(filter->args), p); + if (rv != APR_SUCCESS) { + return "cmd= parse error"; + } + } + else + { + /* simple path */ + /* 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; +} + +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; + char *normalized_name; + ef_filter_t *filter; + + name = ap_getword_white(cmd->pool, &args); + if (!name) { + return "Filter name not found"; + } + + /* 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); + } + + filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t)); + filter->name = name; + filter->mode = OUTPUT_FILTER; + filter->ftype = AP_FTYPE_RESOURCE; + apr_hash_set(conf->h, normalized_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, "ftype=", 6)) { + args += 6; + token = ap_getword_white(cmd->pool, &args); + filter->ftype = atoi(token); + continue; + } + + if (!strncasecmp(args, "enableenv=", 10)) { + args += 10; + token = ap_getword_white(cmd->pool, &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); + 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, NULL, filter->ftype); + } + 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, filter->ftype); + } + 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 SetInputFilter/SetOutputFilter */ + "valid options: LogStderr, NoLogStderr"), + AP_INIT_RAW_ARGS("ExtFilterDefine", + define_filter, + NULL, + RSRC_CONF, + "Define an external filter"), + {NULL} +}; + +static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s) +{ + main_server = main_s; + return OK; +} + +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, + 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_core_module_config(r->per_dir_config); + apr_status_t rv; + +#ifdef RLIMIT_CPU + rv = apr_procattr_limit_set(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_procattr_limit_set(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_procattr_limit_set(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) +{ + 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 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)%pm: %s\n", + time_str, + r->useragent_ip, + err, + &err, + description); +} + +/* 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; + const char * const *env; + + ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc)); + + rc = apr_procattr_create(&ctx->procattr, ctx->p); + ap_assert(rc == APR_SUCCESS); + + rc = apr_procattr_io_set(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_procattr_child_err_set(ctx->procattr, + f->r->server->error_log, /* stderr in child */ + NULL); + 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) { + /* QUERY_STRING is added by ap_add_cgi_vars */ + char *arg_copy = apr_pstrdup(f->r->pool, f->r->args); + ap_unescape_url(arg_copy); + apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED", + ap_escape_shell_cmd(f->r->pool, arg_copy)); + } + + 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, + env, /* environment */ + ctx->procattr, + ctx->p); + if (rc != APR_SUCCESS) { + 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; + } + + apr_pool_note_subprocess(ctx->p, ctx->proc, APR_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_pool_cleanup_register(ctx->p, ctx->proc->in, + apr_pool_cleanup_null, /* other mechanism */ + ef_close_file); + +#if APR_FILES_AS_SOCKETS + { + apr_pollfd_t pfd = { 0 }; + + rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0); + ap_assert(rc == APR_SUCCESS); + + 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); + + pfd.reqevents = APR_POLLIN; + pfd.desc.f = ctx->proc->out; + rc = apr_pollset_add(ctx->pollset, &pfd); + 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 *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 ExtFilterInType %s " + "ExtFilterOuttype %s", + log_stderr_str, preserve_content_length_str, + intype_str, outtype_str); +} + +static ef_filter_t *find_filter_def(const server_rec *s, const char *fname) +{ + ef_server_t *sc; + ef_filter_t *f; + + sc = ap_get_module_config(s->module_config, &ext_filter_module); + f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING); + if (!f && s != main_server) { + s = main_server; + sc = ap_get_module_config(s->module_config, &ext_filter_module); + f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING); + } + return f; +} + +static apr_status_t init_filter_instance(ap_filter_t *f) +{ + ef_ctx_t *ctx; + ef_dir_t *dc; + 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); + ctx->dc = dc; + /* 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, APLOGNO(01459) + "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) { + const char *ctypes; + + if (ctx->filter->mode == INPUT_FILTER) { + ctypes = apr_table_get(f->r->headers_in, "Content-Type"); + } + else { + ctypes = f->r->content_type; + } + + if (ctypes) { + const char *ctype = ap_getword(f->r->pool, &ctypes, ';'); + + if (strcasecmp(ctx->filter->intype, ctype)) { + /* wrong IMT for us; don't mess with the output */ + ctx->noop = 1; + } + } + else { + ctx->noop = 1; + } + } + if (ctx->filter->enable_env && + !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) { + /* an environment variable that enables the filter isn't set; bail */ + ctx->noop = 1; + } + if (ctx->filter->disable_env && + apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) { + /* an environment variable that disables the filter is set; bail */ + ctx->noop = 1; + } + if (!ctx->noop) { + rv = init_ext_filter_process(f); + if (rv != APR_SUCCESS) { + return rv; + } + if (ctx->filter->outtype && + ctx->filter->outtype != OUTTYPE_UNCHANGED) { + ap_set_content_type(f->r, 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 (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, + f->r->content_type ? f->r->content_type : "(unspecified)", + 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 append it + * to the bucket brigade + */ +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; + apr_size_t len; + char buf[4096]; + apr_status_t rv; + 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)) + lvl = APLOG_DEBUG; + ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01460) + "apr_file_read(child output), len %" APR_SIZE_T_FMT, len); + if (rv != APR_SUCCESS) { + return rv; + } + b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + return APR_SUCCESS; + } + /* 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_size_t len, apr_bucket_brigade *bb) +{ + ef_ctx_t *ctx = f->ctx; + apr_status_t rv; + apr_size_t bytes_written = 0; + apr_size_t tmplen; + + do { + tmplen = len - bytes_written; + rv = apr_file_write_full(ctx->proc->in, + (const char *)data + bytes_written, + tmplen, &tmplen); + bytes_written += tmplen; + if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { + 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 + * to read data from the child process and pass it down to the + * next filter! + */ + rv = drain_available_output(f, bb); + if (APR_STATUS_IS_EAGAIN(rv)) { +#if APR_FILES_AS_SOCKETS + int num_events; + 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 + */ + apr_sleep(apr_time_from_msec(100)); + ap_log_rerror(APLOG_MARK, APLOG_TRACE6, 0, f->r, "apr_sleep()"); +#endif /* APR_FILES_AS_SOCKETS */ + } + else if (rv != APR_SUCCESS) { + return rv; + } + } + } while (bytes_written < len); + return rv; +} + +/* 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; + apr_size_t len; + const char *data; + apr_status_t rv; + char buf[4096]; + apr_bucket *eos = NULL; + apr_bucket_brigade *bb_tmp; + + 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; + } + + if (AP_BUCKET_IS_ERROR(b)) { + apr_bucket *cpy; + apr_bucket_copy(b, &cpy); + APR_BRIGADE_INSERT_TAIL(bb_tmp, cpy); + break; + } + + rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); + if (rv != APR_SUCCESS) { + 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, bb_tmp)) + != APR_SUCCESS) { + return rv; + } + } + + apr_brigade_cleanup(bb); + APR_BRIGADE_CONCAT(bb, bb_tmp); + apr_brigade_destroy(bb_tmp); + + 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, 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... + */ + rv = apr_file_pipe_timeout_set(ctx->proc->out, + r->server->timeout); + if (rv) { + 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)) + lvl = APLOG_ERR; + ap_log_rerror(APLOG_MARK, lvl, rv, r, APLOGNO(01466) + "apr_file_read(child output), len %" APR_SIZE_T_FMT, len); + 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) { + b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + } + } while (rv == APR_SUCCESS); + + if (!APR_STATUS_IS_EOF(rv)) { + return rv; + } + + if (eos) { + b = apr_bucket_eos_create(c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, b); + ctx->hit_eos = 1; + } + + return APR_SUCCESS; +} + +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; + + 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) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01468) + "ef_unified_filter() failed"); + } + + 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 apr_status_t 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; + + /* just get out of the way of things we don't want. */ + if (mode != AP_MODE_READBYTES) { + return 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, 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); + } + else { + f->r->status = HTTP_INTERNAL_SERVER_ERROR; + return HTTP_INTERNAL_SERVER_ERROR; + } + } + ctx = f->ctx; + } + + if (ctx->hit_eos) { + /* Match behaviour of HTTP_IN if filter is re-invoked after + * hitting EOS: give back another EOS. */ + apr_bucket *e = apr_bucket_eos_create(f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + return APR_SUCCESS; + } + + 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; +} + +AP_DECLARE_MODULE(ext_filter) = +{ + STANDARD20_MODULE_STUFF, + create_ef_dir_conf, + merge_ef_dir_conf, + create_ef_server_conf, + NULL, + cmds, + register_hooks +}; |