mod_ext_filter.c revision 7184de27ec1d62a83c41cdeac0953ca9fd661e8c
/* 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
*
*
* 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;
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 log_stderr;
int onfail;
} 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 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;
char *normalized_name;
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.
*/
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: 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) || \
#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);
}
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);
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 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
*/
}
}
if (APLOGrtrace1(f->r)) {
"%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) {
int lvl = APLOG_TRACE5;
lvl = APLOG_DEBUG;
"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);
"apr_pollset_poll()");
/* some error such as APR_TIMEUP */
return rv;
}
"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
*/
#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 {
int lvl = APLOG_TRACE6;
"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) {
"can't initialise output filter %s: %s",
}
else {
apr_bucket *e;
f->r->status_line = "500 Internal Server Error";
f->c->bucket_alloc);
e = apr_bucket_eos_create(f->c->bucket_alloc);
return AP_FILTER_ERROR;
}
}
}
}
if (rv != APR_SUCCESS) {
"ef_unified_filter() failed");
}
"ap_pass_brigade() failed");
}
return rv;
}
{
if (!ctx) {
"can't initialise input filter %s: %s",
}
else {
f->r->status = HTTP_INTERNAL_SERVER_ERROR;
return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
}
if (rv != APR_SUCCESS) {
return rv;
}
return rv;
}
{
NULL,
cmds,
};