mod_ext_filter.c revision 762d10071eea0a55eed24e7c3da72c693db2d944
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2003 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
* 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"
#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"
#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;
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
} 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;
int noop;
#endif
} ef_ctx_t;
static const server_rec *main_server;
#define DBGLVL_SHOWOPTIONS 1
#define DBGLVL_ERRORCHECK 2
#define DBGLVL_GORY 9
#define ERRFN_USERDATA_KEY "EXTFILTCHILDERRFN"
{
return dc;
}
{
conf->p = p;
return conf;
}
{
}
else {
}
}
else {
}
return a;
}
const char *arg)
{
}
}
dc->log_stderr = 0;
}
else {
"Invalid ExtFilterOptions option: ",
arg,
NULL);
}
return NULL;
}
{
if (**args == '"') {
char *parms;
int escaping = 0;
++*args; /* move past leading " */
/* find true end of args string (accounting for escaped quotes) */
if (escaping) {
escaping = 0;
}
else if (**args == '\\') {
escaping = 1;
}
++*args;
}
if (**args != '"') {
return "Expected cmd= delimiter";
}
/* copy *just* the arg string for parsing, */
++*args; /* move past trailing " */
/* parse and tokenize the args. */
if (rv != APR_SUCCESS) {
return "cmd= parse error";
}
}
else
{
/* simple path */
/* Allocate space for two argv pointers and parse the args. */
}
return "Invalid cmd= parameter";
}
return NULL;
}
{
const char *token;
const char *name;
if (!name) {
return "Filter name not found";
}
name);
}
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"
*/
}
else {
"mangled argument `%s'",
token);
}
continue;
}
args += 5;
}
}
else {
token);
}
continue;
}
args += 6;
continue;
}
args += 10;
continue;
}
args += 11;
continue;
}
args += 7;
continue;
}
args += 8;
continue;
}
args += 4;
return token;
}
continue;
}
args);
}
/* parsing is done... register the filter
*/
/* XXX need a way to ensure uniqueness among all filters */
}
/* XXX need a way to ensure uniqueness among all filters */
}
else {
}
return NULL;
}
static const command_rec cmds[] =
{
AP_INIT_ITERATE("ExtFilterOptions",
NULL,
ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
"valid options: DebugLevel=n, LogStderr, NoLogStderr"),
AP_INIT_RAW_ARGS("ExtFilterDefine",
NULL,
"Define an external filter"),
{NULL}
};
{
return OK;
}
static void register_hooks(apr_pool_t *p)
{
}
{
#if defined(RLIMIT_CPU) || defined(RLIMIT_NPROC) || \
&core_module);
#ifdef RLIMIT_CPU
#endif
#endif
#ifdef RLIMIT_NPROC
#endif
#endif /* if at least one limit defined */
return APR_SUCCESS;
}
{
return apr_file_close(vfile);
}
{
request_rec *r;
void *vr;
char errbuf[200];
char time_str[APR_CTIME_LEN];
r = vr;
"[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
r->connection->remote_ip,
err,
}
/* init_ext_filter_process: get the external filter process going
* This is per-filter-instance (i.e., per-request) initialization.
*/
{
const char * const *env;
if (dc->log_stderr > 0) {
NULL);
}
}
/* 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);
if (f->r->args) {
/* QUERY_STRING is added by ap_add_cgi_vars */
}
f->r->subprocess_env);
env, /* environment */
ctx->p);
if (rc != APR_SUCCESS) {
"couldn't create child process to run `%s'",
return rc;
}
/* 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_null, /* other mechanism */
{
apr_pollfd_t pfd = { 0 };
}
#endif
return APR_SUCCESS;
}
{
"NoLogStderr" : "LogStderr";
"PreservesContentLength" : "!PreserveContentLength";
return apr_psprintf(p,
"ExtFilterOptions %s %s %s ExtFilterInType %s "
"ExtFilterOuttype %s",
}
{
ef_filter_t *f;
if (!f && s != main_server) {
s = main_server;
}
return f;
}
{
/* look for the user-defined filter */
"couldn't find definition of filter '%s'",
return APR_EINVAL;
}
const char *ctypes;
}
else {
ctypes = f->r->content_type;
}
if (ctypes) {
/* wrong IMT for us; don't mess with the output */
}
}
else {
}
}
/* an environment variable that enables the filter isn't set; bail */
}
/* an environment variable that disables the filter is set; bail */
}
rv = init_ext_filter_process(f);
if (rv != APR_SUCCESS) {
return rv;
}
}
/* nasty, but needed to avoid confusing the browser
*/
}
}
"%sfiltering `%s' of type `%s' through `%s', cfg %s",
}
return APR_SUCCESS;
}
/* drain_available_output():
*
* if any data is available from the filter, read it and append it
* to the the bucket brigade
*/
{
request_rec *r = f->r;
conn_rec *c = r->connection;
char buf[4096];
apr_bucket *b;
while (1) {
buf,
&len);
"apr_file_read(child output), len %" APR_SIZE_T_FMT,
}
if (rv != APR_SUCCESS) {
return rv;
}
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;
}
{
apr_size_t bytes_written = 0;
do {
(const char *)data + bytes_written,
&tmplen);
bytes_written += tmplen;
"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!
*/
if (APR_STATUS_IS_EAGAIN(rv)) {
int num_events;
const apr_pollfd_t *pdesc;
&num_events, &pdesc);
rv, f->r, "apr_pollset_poll()");
}
/* 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
*/
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)
*/
{
request_rec *r = f->r;
conn_rec *c = r->connection;
apr_bucket *b;
const char *data;
char buf[4096];
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 (rv != APR_SUCCESS) {
return rv;
}
/* Good cast, we just tested len isn't negative */
if (len > 0 &&
!= APR_SUCCESS) {
return rv;
}
}
if (eos) {
/* close the child's stdin to signal that no more data is coming;
* that will cause the child to finish generating output
*/
"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...
*/
if (rv) {
"apr_file_pipe_timeout_set(child output)");
return rv;
}
}
do {
buf,
&len);
"apr_file_read(child output), len %" APR_SIZE_T_FMT,
}
if (APR_STATUS_IS_EAGAIN(rv)) {
if (eos) {
/* should not occur, because we have an APR timeout in place */
}
return APR_SUCCESS;
}
if (rv == APR_SUCCESS) {
}
} while (rv == APR_SUCCESS);
if (!APR_STATUS_IS_EOF(rv)) {
return rv;
}
if (eos) {
b = apr_bucket_eos_create(c->bucket_alloc);
}
return APR_SUCCESS;
}
{
request_rec *r = f->r;
if (!ctx) {
return rv;
}
}
}
if (rv != APR_SUCCESS) {
"ef_unified_filter() failed");
}
"ap_pass_brigade() failed");
}
return rv;
}
{
if (!ctx) {
return rv;
}
}
}
if (rv != APR_SUCCESS) {
return rv;
}
return rv;
}
{
NULL,
cmds,
};