~ubuntu-branches/ubuntu/feisty/apache2/feisty

« back to all changes in this revision

Viewing changes to modules/filters/mod_ext_filter.c

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Barth
  • Date: 2006-12-09 21:05:45 UTC
  • mfrom: (0.6.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20061209210545-h70s0xaqc2v8vqr2
Tags: 2.2.3-3.2
* Non-maintainer upload.
* 043_ajp_connection_reuse: Patch from upstream Bugzilla, fixing a critical
  issue with regard to connection reuse in mod_proxy_ajp.
  Closes: #396265

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* Licensed to the Apache Software Foundation (ASF) under one or more
 
2
 * contributor license agreements.  See the NOTICE file distributed with
 
3
 * this work for additional information regarding copyright ownership.
 
4
 * The ASF licenses this file to You under the Apache License, Version 2.0
 
5
 * (the "License"); you may not use this file except in compliance with
 
6
 * the License.  You may obtain a copy of the License at
 
7
 *
 
8
 *     http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
10
 * Unless required by applicable law or agreed to in writing, software
 
11
 * distributed under the License is distributed on an "AS IS" BASIS,
 
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
 * See the License for the specific language governing permissions and
 
14
 * limitations under the License.
 
15
 */
 
16
 
 
17
/*
 
18
 * mod_ext_filter allows Unix-style filters to filter http content.
 
19
 */
 
20
 
 
21
#include "httpd.h"
 
22
#include "http_config.h"
 
23
#include "http_log.h"
 
24
#include "http_protocol.h"
 
25
#define CORE_PRIVATE
 
26
#include "http_core.h"
 
27
#include "apr_buckets.h"
 
28
#include "util_filter.h"
 
29
#include "util_script.h"
 
30
#include "util_time.h"
 
31
#include "apr_strings.h"
 
32
#include "apr_hash.h"
 
33
#include "apr_lib.h"
 
34
#include "apr_poll.h"
 
35
#define APR_WANT_STRFUNC
 
36
#include "apr_want.h"
 
37
 
 
38
typedef struct ef_server_t {
 
39
    apr_pool_t *p;
 
40
    apr_hash_t *h;
 
41
} ef_server_t;
 
42
 
 
43
typedef struct ef_filter_t {
 
44
    const char *name;
 
45
    enum {INPUT_FILTER=1, OUTPUT_FILTER} mode;
 
46
    ap_filter_type ftype;
 
47
    const char *command;
 
48
    const char *enable_env;
 
49
    const char *disable_env;
 
50
    char **args;
 
51
    const char *intype;             /* list of IMTs we process (well, just one for now) */
 
52
#define INTYPE_ALL (char *)1
 
53
    const char *outtype;            /* IMT of filtered output */
 
54
#define OUTTYPE_UNCHANGED (char *)1
 
55
    int preserves_content_length;
 
56
} ef_filter_t;
 
57
 
 
58
typedef struct ef_dir_t {
 
59
    int debug;
 
60
    int log_stderr;
 
61
} ef_dir_t;
 
62
 
 
63
typedef struct ef_ctx_t {
 
64
    apr_pool_t *p;
 
65
    apr_proc_t *proc;
 
66
    apr_procattr_t *procattr;
 
67
    ef_dir_t *dc;
 
68
    ef_filter_t *filter;
 
69
    int noop;
 
70
#if APR_FILES_AS_SOCKETS
 
71
    apr_pollset_t *pollset;
 
72
#endif
 
73
} ef_ctx_t;
 
74
 
 
75
module AP_MODULE_DECLARE_DATA ext_filter_module;
 
76
static const server_rec *main_server;
 
77
 
 
78
static apr_status_t ef_output_filter(ap_filter_t *, apr_bucket_brigade *);
 
79
static apr_status_t ef_input_filter(ap_filter_t *, apr_bucket_brigade *,
 
80
                                    ap_input_mode_t, apr_read_type_e,
 
81
                                    apr_off_t);
 
82
 
 
83
#define DBGLVL_SHOWOPTIONS         1
 
84
#define DBGLVL_ERRORCHECK          2
 
85
#define DBGLVL_GORY                9
 
86
 
 
87
#define ERRFN_USERDATA_KEY         "EXTFILTCHILDERRFN"
 
88
 
 
89
static void *create_ef_dir_conf(apr_pool_t *p, char *dummy)
 
90
{
 
91
    ef_dir_t *dc = (ef_dir_t *)apr_pcalloc(p, sizeof(ef_dir_t));
 
92
 
 
93
    dc->debug = -1;
 
94
    dc->log_stderr = -1;
 
95
 
 
96
    return dc;
 
97
}
 
98
 
 
99
static void *create_ef_server_conf(apr_pool_t *p, server_rec *s)
 
100
{
 
101
    ef_server_t *conf;
 
102
 
 
103
    conf = (ef_server_t *)apr_pcalloc(p, sizeof(ef_server_t));
 
104
    conf->p = p;
 
105
    conf->h = apr_hash_make(conf->p);
 
106
    return conf;
 
107
}
 
108
 
 
109
static void *merge_ef_dir_conf(apr_pool_t *p, void *basev, void *overridesv)
 
110
{
 
111
    ef_dir_t *a = (ef_dir_t *)apr_pcalloc (p, sizeof(ef_dir_t));
 
112
    ef_dir_t *base = (ef_dir_t *)basev, *over = (ef_dir_t *)overridesv;
 
113
 
 
114
    if (over->debug != -1) {        /* if admin coded something... */
 
115
        a->debug = over->debug;
 
116
    }
 
117
    else {
 
118
        a->debug = base->debug;
 
119
    }
 
120
 
 
121
    if (over->log_stderr != -1) {   /* if admin coded something... */
 
122
        a->log_stderr = over->log_stderr;
 
123
    }
 
124
    else {
 
125
        a->log_stderr = base->log_stderr;
 
126
    }
 
127
 
 
128
    return a;
 
129
}
 
130
 
 
131
static const char *add_options(cmd_parms *cmd, void *in_dc,
 
132
                               const char *arg)
 
133
{
 
134
    ef_dir_t *dc = in_dc;
 
135
 
 
136
    if (!strncasecmp(arg, "DebugLevel=", 11)) {
 
137
        dc->debug = atoi(arg + 11);
 
138
    }
 
139
    else if (!strcasecmp(arg, "LogStderr")) {
 
140
        dc->log_stderr = 1;
 
141
    }
 
142
    else if (!strcasecmp(arg, "NoLogStderr")) {
 
143
        dc->log_stderr = 0;
 
144
    }
 
145
    else {
 
146
        return apr_pstrcat(cmd->temp_pool,
 
147
                           "Invalid ExtFilterOptions option: ",
 
148
                           arg,
 
149
                           NULL);
 
150
    }
 
151
 
 
152
    return NULL;
 
153
}
 
154
 
 
155
static const char *parse_cmd(apr_pool_t *p, const char **args, ef_filter_t *filter)
 
156
{
 
157
    if (**args == '"') {
 
158
        const char *start = *args + 1;
 
159
        char *parms;
 
160
        int escaping = 0;
 
161
        apr_status_t rv;
 
162
 
 
163
        ++*args; /* move past leading " */
 
164
        /* find true end of args string (accounting for escaped quotes) */
 
165
        while (**args && (**args != '"' || (**args == '"' && escaping))) {
 
166
            if (escaping) {
 
167
                escaping = 0;
 
168
            }
 
169
            else if (**args == '\\') {
 
170
                escaping = 1;
 
171
            }
 
172
            ++*args;
 
173
        }
 
174
        if (**args != '"') {
 
175
            return "Expected cmd= delimiter";
 
176
        }
 
177
        /* copy *just* the arg string for parsing, */
 
178
        parms = apr_pstrndup(p, start, *args - start);
 
179
        ++*args; /* move past trailing " */
 
180
 
 
181
        /* parse and tokenize the args. */
 
182
        rv = apr_tokenize_to_argv(parms, &(filter->args), p);
 
183
        if (rv != APR_SUCCESS) {
 
184
            return "cmd= parse error";
 
185
        }
 
186
    }
 
187
    else
 
188
    {
 
189
        /* simple path */
 
190
        /* Allocate space for two argv pointers and parse the args. */
 
191
        filter->args = (char **)apr_palloc(p, 2 * sizeof(char *));
 
192
        filter->args[0] = ap_getword_white(p, args);
 
193
        filter->args[1] = NULL; /* end of args */
 
194
    }
 
195
    if (!filter->args[0]) {
 
196
        return "Invalid cmd= parameter";
 
197
    }
 
198
    filter->command = filter->args[0];
 
199
 
 
200
    return NULL;
 
201
}
 
202
 
 
203
static const char *define_filter(cmd_parms *cmd, void *dummy, const char *args)
 
204
{
 
205
    ef_server_t *conf = ap_get_module_config(cmd->server->module_config,
 
206
                                             &ext_filter_module);
 
207
    const char *token;
 
208
    const char *name;
 
209
    ef_filter_t *filter;
 
210
 
 
211
    name = ap_getword_white(cmd->pool, &args);
 
212
    if (!name) {
 
213
        return "Filter name not found";
 
214
    }
 
215
 
 
216
    if (apr_hash_get(conf->h, name, APR_HASH_KEY_STRING)) {
 
217
        return apr_psprintf(cmd->pool, "ExtFilter %s is already defined",
 
218
                            name);
 
219
    }
 
220
 
 
221
    filter = (ef_filter_t *)apr_pcalloc(conf->p, sizeof(ef_filter_t));
 
222
    filter->name = name;
 
223
    filter->mode = OUTPUT_FILTER;
 
224
    filter->ftype = AP_FTYPE_RESOURCE;
 
225
    apr_hash_set(conf->h, name, APR_HASH_KEY_STRING, filter);
 
226
 
 
227
    while (*args) {
 
228
        while (apr_isspace(*args)) {
 
229
            ++args;
 
230
        }
 
231
 
 
232
        /* Nasty parsing...  I wish I could simply use ap_getword_white()
 
233
         * here and then look at the token, but ap_getword_white() doesn't
 
234
         * do the right thing when we have cmd="word word word"
 
235
         */
 
236
        if (!strncasecmp(args, "preservescontentlength", 22)) {
 
237
            token = ap_getword_white(cmd->pool, &args);
 
238
            if (!strcasecmp(token, "preservescontentlength")) {
 
239
                filter->preserves_content_length = 1;
 
240
            }
 
241
            else {
 
242
                return apr_psprintf(cmd->pool,
 
243
                                    "mangled argument `%s'",
 
244
                                    token);
 
245
            }
 
246
            continue;
 
247
        }
 
248
 
 
249
        if (!strncasecmp(args, "mode=", 5)) {
 
250
            args += 5;
 
251
            token = ap_getword_white(cmd->pool, &args);
 
252
            if (!strcasecmp(token, "output")) {
 
253
                filter->mode = OUTPUT_FILTER;
 
254
            }
 
255
            else if (!strcasecmp(token, "input")) {
 
256
                filter->mode = INPUT_FILTER;
 
257
            }
 
258
            else {
 
259
                return apr_psprintf(cmd->pool, "Invalid mode: `%s'",
 
260
                                    token);
 
261
            }
 
262
            continue;
 
263
        }
 
264
 
 
265
        if (!strncasecmp(args, "ftype=", 6)) {
 
266
            args += 6;
 
267
            token = ap_getword_white(cmd->pool, &args);
 
268
            filter->ftype = atoi(token);
 
269
            continue;
 
270
        }
 
271
 
 
272
        if (!strncasecmp(args, "enableenv=", 10)) {
 
273
            args += 10;
 
274
            token = ap_getword_white(cmd->pool, &args);
 
275
            filter->enable_env = token;
 
276
            continue;
 
277
        }
 
278
 
 
279
        if (!strncasecmp(args, "disableenv=", 11)) {
 
280
            args += 11;
 
281
            token = ap_getword_white(cmd->pool, &args);
 
282
            filter->disable_env = token;
 
283
            continue;
 
284
        }
 
285
 
 
286
        if (!strncasecmp(args, "intype=", 7)) {
 
287
            args += 7;
 
288
            filter->intype = ap_getword_white(cmd->pool, &args);
 
289
            continue;
 
290
        }
 
291
 
 
292
        if (!strncasecmp(args, "outtype=", 8)) {
 
293
            args += 8;
 
294
            filter->outtype = ap_getword_white(cmd->pool, &args);
 
295
            continue;
 
296
        }
 
297
 
 
298
        if (!strncasecmp(args, "cmd=", 4)) {
 
299
            args += 4;
 
300
            if ((token = parse_cmd(cmd->pool, &args, filter))) {
 
301
                return token;
 
302
            }
 
303
            continue;
 
304
        }
 
305
 
 
306
        return apr_psprintf(cmd->pool, "Unexpected parameter: `%s'",
 
307
                            args);
 
308
    }
 
309
 
 
310
    /* parsing is done...  register the filter
 
311
     */
 
312
    if (filter->mode == OUTPUT_FILTER) {
 
313
        /* XXX need a way to ensure uniqueness among all filters */
 
314
        ap_register_output_filter(filter->name, ef_output_filter, NULL, filter->ftype);
 
315
    }
 
316
    else if (filter->mode == INPUT_FILTER) {
 
317
        /* XXX need a way to ensure uniqueness among all filters */
 
318
        ap_register_input_filter(filter->name, ef_input_filter, NULL, filter->ftype);
 
319
    }
 
320
    else {
 
321
        ap_assert(1 != 1); /* we set the field wrong somehow */
 
322
    }
 
323
 
 
324
    return NULL;
 
325
}
 
326
 
 
327
static const command_rec cmds[] =
 
328
{
 
329
    AP_INIT_ITERATE("ExtFilterOptions",
 
330
                    add_options,
 
331
                    NULL,
 
332
                    ACCESS_CONF, /* same as SetInputFilter/SetOutputFilter */
 
333
                    "valid options: DebugLevel=n, LogStderr, NoLogStderr"),
 
334
    AP_INIT_RAW_ARGS("ExtFilterDefine",
 
335
                     define_filter,
 
336
                     NULL,
 
337
                     RSRC_CONF,
 
338
                     "Define an external filter"),
 
339
    {NULL}
 
340
};
 
341
 
 
342
static int ef_init(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *main_s)
 
343
{
 
344
    main_server = main_s;
 
345
    return OK;
 
346
}
 
347
 
 
348
static void register_hooks(apr_pool_t *p)
 
349
{
 
350
    ap_hook_post_config(ef_init, NULL, NULL, APR_HOOK_MIDDLE);
 
351
}
 
352
 
 
353
static apr_status_t set_resource_limits(request_rec *r,
 
354
                                        apr_procattr_t *procattr)
 
355
{
 
356
#if defined(RLIMIT_CPU)  || defined(RLIMIT_NPROC) || \
 
357
    defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined (RLIMIT_AS)
 
358
    core_dir_config *conf =
 
359
        (core_dir_config *)ap_get_module_config(r->per_dir_config,
 
360
                                                &core_module);
 
361
    apr_status_t rv;
 
362
 
 
363
#ifdef RLIMIT_CPU
 
364
    rv = apr_procattr_limit_set(procattr, APR_LIMIT_CPU, conf->limit_cpu);
 
365
    ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
 
366
#endif
 
367
#if defined(RLIMIT_DATA) || defined(RLIMIT_VMEM) || defined(RLIMIT_AS)
 
368
    rv = apr_procattr_limit_set(procattr, APR_LIMIT_MEM, conf->limit_mem);
 
369
    ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
 
370
#endif
 
371
#ifdef RLIMIT_NPROC
 
372
    rv = apr_procattr_limit_set(procattr, APR_LIMIT_NPROC, conf->limit_nproc);
 
373
    ap_assert(rv == APR_SUCCESS); /* otherwise, we're out of sync with APR */
 
374
#endif
 
375
 
 
376
#endif /* if at least one limit defined */
 
377
 
 
378
    return APR_SUCCESS;
 
379
}
 
380
 
 
381
static apr_status_t ef_close_file(void *vfile)
 
382
{
 
383
    return apr_file_close(vfile);
 
384
}
 
385
 
 
386
static void child_errfn(apr_pool_t *pool, apr_status_t err, const char *description)
 
387
{
 
388
    request_rec *r;
 
389
    void *vr;
 
390
    apr_file_t *stderr_log;
 
391
    char errbuf[200];
 
392
    char time_str[APR_CTIME_LEN];
 
393
 
 
394
    apr_pool_userdata_get(&vr, ERRFN_USERDATA_KEY, pool);
 
395
    r = vr;
 
396
    apr_file_open_stderr(&stderr_log, pool);
 
397
    ap_recent_ctime(time_str, apr_time_now());
 
398
    apr_file_printf(stderr_log,
 
399
                    "[%s] [client %s] mod_ext_filter (%d)%s: %s\n",
 
400
                    time_str,
 
401
                    r->connection->remote_ip,
 
402
                    err,
 
403
                    apr_strerror(err, errbuf, sizeof(errbuf)),
 
404
                    description);
 
405
}
 
406
 
 
407
/* init_ext_filter_process: get the external filter process going
 
408
 * This is per-filter-instance (i.e., per-request) initialization.
 
409
 */
 
410
static apr_status_t init_ext_filter_process(ap_filter_t *f)
 
411
{
 
412
    ef_ctx_t *ctx = f->ctx;
 
413
    apr_status_t rc;
 
414
    ef_dir_t *dc = ctx->dc;
 
415
    const char * const *env;
 
416
 
 
417
    ctx->proc = apr_pcalloc(ctx->p, sizeof(*ctx->proc));
 
418
 
 
419
    rc = apr_procattr_create(&ctx->procattr, ctx->p);
 
420
    ap_assert(rc == APR_SUCCESS);
 
421
 
 
422
    rc = apr_procattr_io_set(ctx->procattr,
 
423
                            APR_CHILD_BLOCK,
 
424
                            APR_CHILD_BLOCK,
 
425
                            APR_CHILD_BLOCK);
 
426
    ap_assert(rc == APR_SUCCESS);
 
427
 
 
428
    rc = set_resource_limits(f->r, ctx->procattr);
 
429
    ap_assert(rc == APR_SUCCESS);
 
430
 
 
431
    if (dc->log_stderr > 0) {
 
432
        rc = apr_procattr_child_err_set(ctx->procattr,
 
433
                                      f->r->server->error_log, /* stderr in child */
 
434
                                      NULL);
 
435
        ap_assert(rc == APR_SUCCESS);
 
436
    }
 
437
 
 
438
    rc = apr_procattr_child_errfn_set(ctx->procattr, child_errfn);
 
439
    ap_assert(rc == APR_SUCCESS);
 
440
    apr_pool_userdata_set(f->r, ERRFN_USERDATA_KEY, apr_pool_cleanup_null, ctx->p);
 
441
 
 
442
    if (dc->debug >= DBGLVL_ERRORCHECK) {
 
443
        rc = apr_procattr_error_check_set(ctx->procattr, 1);
 
444
        ap_assert(rc == APR_SUCCESS);
 
445
    }
 
446
 
 
447
    /* add standard CGI variables as well as DOCUMENT_URI, DOCUMENT_PATH_INFO,
 
448
     * and QUERY_STRING_UNESCAPED
 
449
     */
 
450
    ap_add_cgi_vars(f->r);
 
451
    ap_add_common_vars(f->r);
 
452
    apr_table_setn(f->r->subprocess_env, "DOCUMENT_URI", f->r->uri);
 
453
    apr_table_setn(f->r->subprocess_env, "DOCUMENT_PATH_INFO", f->r->path_info);
 
454
    if (f->r->args) {
 
455
            /* QUERY_STRING is added by ap_add_cgi_vars */
 
456
        char *arg_copy = apr_pstrdup(f->r->pool, f->r->args);
 
457
        ap_unescape_url(arg_copy);
 
458
        apr_table_setn(f->r->subprocess_env, "QUERY_STRING_UNESCAPED",
 
459
                       ap_escape_shell_cmd(f->r->pool, arg_copy));
 
460
    }
 
461
 
 
462
    env = (const char * const *) ap_create_environment(ctx->p,
 
463
                                                       f->r->subprocess_env);
 
464
 
 
465
    rc = apr_proc_create(ctx->proc,
 
466
                            ctx->filter->command,
 
467
                            (const char * const *)ctx->filter->args,
 
468
                            env, /* environment */
 
469
                            ctx->procattr,
 
470
                            ctx->p);
 
471
    if (rc != APR_SUCCESS) {
 
472
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, f->r,
 
473
                      "couldn't create child process to run `%s'",
 
474
                      ctx->filter->command);
 
475
        return rc;
 
476
    }
 
477
 
 
478
    apr_pool_note_subprocess(ctx->p, ctx->proc, APR_KILL_AFTER_TIMEOUT);
 
479
 
 
480
    /* We don't want the handle to the child's stdin inherited by any
 
481
     * other processes created by httpd.  Otherwise, when we close our
 
482
     * handle, the child won't see EOF because another handle will still
 
483
     * be open.
 
484
     */
 
485
 
 
486
    apr_pool_cleanup_register(ctx->p, ctx->proc->in,
 
487
                         apr_pool_cleanup_null, /* other mechanism */
 
488
                         ef_close_file);
 
489
 
 
490
#if APR_FILES_AS_SOCKETS
 
491
    {
 
492
        apr_pollfd_t pfd = { 0 };
 
493
 
 
494
        rc = apr_pollset_create(&ctx->pollset, 2, ctx->p, 0);
 
495
        ap_assert(rc == APR_SUCCESS);
 
496
 
 
497
        pfd.p         = ctx->p;
 
498
        pfd.desc_type = APR_POLL_FILE;
 
499
        pfd.reqevents = APR_POLLOUT;
 
500
        pfd.desc.f    = ctx->proc->in;
 
501
        rc = apr_pollset_add(ctx->pollset, &pfd);
 
502
        ap_assert(rc == APR_SUCCESS);
 
503
 
 
504
        pfd.reqevents = APR_POLLIN;
 
505
        pfd.desc.f    = ctx->proc->out;
 
506
        rc = apr_pollset_add(ctx->pollset, &pfd);
 
507
        ap_assert(rc == APR_SUCCESS);
 
508
    }
 
509
#endif
 
510
 
 
511
    return APR_SUCCESS;
 
512
}
 
513
 
 
514
static const char *get_cfg_string(ef_dir_t *dc, ef_filter_t *filter, apr_pool_t *p)
 
515
{
 
516
    const char *debug_str = dc->debug == -1 ?
 
517
        "DebugLevel=0" : apr_psprintf(p, "DebugLevel=%d", dc->debug);
 
518
    const char *log_stderr_str = dc->log_stderr < 1 ?
 
519
        "NoLogStderr" : "LogStderr";
 
520
    const char *preserve_content_length_str = filter->preserves_content_length ?
 
521
        "PreservesContentLength" : "!PreserveContentLength";
 
522
    const char *intype_str = !filter->intype ?
 
523
        "*/*" : filter->intype;
 
524
    const char *outtype_str = !filter->outtype ?
 
525
        "(unchanged)" : filter->outtype;
 
526
 
 
527
    return apr_psprintf(p,
 
528
                        "ExtFilterOptions %s %s %s ExtFilterInType %s "
 
529
                        "ExtFilterOuttype %s",
 
530
                        debug_str, log_stderr_str, preserve_content_length_str,
 
531
                        intype_str, outtype_str);
 
532
}
 
533
 
 
534
static ef_filter_t *find_filter_def(const server_rec *s, const char *fname)
 
535
{
 
536
    ef_server_t *sc;
 
537
    ef_filter_t *f;
 
538
 
 
539
    sc = ap_get_module_config(s->module_config, &ext_filter_module);
 
540
    f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
 
541
    if (!f && s != main_server) {
 
542
        s = main_server;
 
543
        sc = ap_get_module_config(s->module_config, &ext_filter_module);
 
544
        f = apr_hash_get(sc->h, fname, APR_HASH_KEY_STRING);
 
545
    }
 
546
    return f;
 
547
}
 
548
 
 
549
static apr_status_t init_filter_instance(ap_filter_t *f)
 
550
{
 
551
    ef_ctx_t *ctx;
 
552
    ef_dir_t *dc;
 
553
    apr_status_t rv;
 
554
 
 
555
    f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(ef_ctx_t));
 
556
    dc = ap_get_module_config(f->r->per_dir_config,
 
557
                              &ext_filter_module);
 
558
    ctx->dc = dc;
 
559
    /* look for the user-defined filter */
 
560
    ctx->filter = find_filter_def(f->r->server, f->frec->name);
 
561
    if (!ctx->filter) {
 
562
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r,
 
563
                      "couldn't find definition of filter '%s'",
 
564
                      f->frec->name);
 
565
        return APR_EINVAL;
 
566
    }
 
567
    ctx->p = f->r->pool;
 
568
    if (ctx->filter->intype &&
 
569
        ctx->filter->intype != INTYPE_ALL) {
 
570
        const char *ctypes;
 
571
 
 
572
        if (ctx->filter->mode == INPUT_FILTER) {
 
573
            ctypes = apr_table_get(f->r->headers_in, "Content-Type");
 
574
        }
 
575
        else {
 
576
            ctypes = f->r->content_type;
 
577
        }
 
578
 
 
579
        if (ctypes) {
 
580
            const char *ctype = ap_getword(f->r->pool, &ctypes, ';');
 
581
 
 
582
            if (strcasecmp(ctx->filter->intype, ctype)) {
 
583
                /* wrong IMT for us; don't mess with the output */
 
584
                ctx->noop = 1;
 
585
            }
 
586
        }
 
587
        else {
 
588
            ctx->noop = 1;
 
589
        }
 
590
    }
 
591
    if (ctx->filter->enable_env &&
 
592
        !apr_table_get(f->r->subprocess_env, ctx->filter->enable_env)) {
 
593
        /* an environment variable that enables the filter isn't set; bail */
 
594
        ctx->noop = 1;
 
595
    }
 
596
    if (ctx->filter->disable_env &&
 
597
        apr_table_get(f->r->subprocess_env, ctx->filter->disable_env)) {
 
598
        /* an environment variable that disables the filter is set; bail */
 
599
        ctx->noop = 1;
 
600
    }
 
601
    if (!ctx->noop) {
 
602
        rv = init_ext_filter_process(f);
 
603
        if (rv != APR_SUCCESS) {
 
604
            return rv;
 
605
        }
 
606
        if (ctx->filter->outtype &&
 
607
            ctx->filter->outtype != OUTTYPE_UNCHANGED) {
 
608
            ap_set_content_type(f->r, ctx->filter->outtype);
 
609
        }
 
610
        if (ctx->filter->preserves_content_length != 1) {
 
611
            /* nasty, but needed to avoid confusing the browser
 
612
             */
 
613
            apr_table_unset(f->r->headers_out, "Content-Length");
 
614
        }
 
615
    }
 
616
 
 
617
    if (dc->debug >= DBGLVL_SHOWOPTIONS) {
 
618
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r,
 
619
                      "%sfiltering `%s' of type `%s' through `%s', cfg %s",
 
620
                      ctx->noop ? "NOT " : "",
 
621
                      f->r->uri ? f->r->uri : f->r->filename,
 
622
                      f->r->content_type ? f->r->content_type : "(unspecified)",
 
623
                      ctx->filter->command,
 
624
                      get_cfg_string(dc, ctx->filter, f->r->pool));
 
625
    }
 
626
 
 
627
    return APR_SUCCESS;
 
628
}
 
629
 
 
630
/* drain_available_output():
 
631
 *
 
632
 * if any data is available from the filter, read it and append it
 
633
 * to the the bucket brigade
 
634
 */
 
635
static apr_status_t drain_available_output(ap_filter_t *f,
 
636
                                           apr_bucket_brigade *bb)
 
637
{
 
638
    request_rec *r = f->r;
 
639
    conn_rec *c = r->connection;
 
640
    ef_ctx_t *ctx = f->ctx;
 
641
    ef_dir_t *dc = ctx->dc;
 
642
    apr_size_t len;
 
643
    char buf[4096];
 
644
    apr_status_t rv;
 
645
    apr_bucket *b;
 
646
 
 
647
    while (1) {
 
648
        len = sizeof(buf);
 
649
        rv = apr_file_read(ctx->proc->out,
 
650
                      buf,
 
651
                      &len);
 
652
        if ((rv && !APR_STATUS_IS_EAGAIN(rv)) ||
 
653
            dc->debug >= DBGLVL_GORY) {
 
654
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
 
655
                          "apr_file_read(child output), len %" APR_SIZE_T_FMT,
 
656
                          !rv ? len : -1);
 
657
        }
 
658
        if (rv != APR_SUCCESS) {
 
659
            return rv;
 
660
        }
 
661
        b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
 
662
        APR_BRIGADE_INSERT_TAIL(bb, b);
 
663
        return APR_SUCCESS;
 
664
    }
 
665
    /* we should never get here; if we do, a bogus error message would be
 
666
     * the least of our problems
 
667
     */
 
668
    return APR_ANONYMOUS;
 
669
}
 
670
 
 
671
static apr_status_t pass_data_to_filter(ap_filter_t *f, const char *data,
 
672
                                        apr_size_t len, apr_bucket_brigade *bb)
 
673
{
 
674
    ef_ctx_t *ctx = f->ctx;
 
675
    ef_dir_t *dc = ctx->dc;
 
676
    apr_status_t rv;
 
677
    apr_size_t bytes_written = 0;
 
678
    apr_size_t tmplen;
 
679
 
 
680
    do {
 
681
        tmplen = len - bytes_written;
 
682
        rv = apr_file_write(ctx->proc->in,
 
683
                       (const char *)data + bytes_written,
 
684
                       &tmplen);
 
685
        bytes_written += tmplen;
 
686
        if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) {
 
687
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r,
 
688
                          "apr_file_write(child input), len %" APR_SIZE_T_FMT,
 
689
                          tmplen);
 
690
            return rv;
 
691
        }
 
692
        if (APR_STATUS_IS_EAGAIN(rv)) {
 
693
            /* XXX handle blocking conditions here...  if we block, we need
 
694
             * to read data from the child process and pass it down to the
 
695
             * next filter!
 
696
             */
 
697
            rv = drain_available_output(f, bb);
 
698
            if (APR_STATUS_IS_EAGAIN(rv)) {
 
699
#if APR_FILES_AS_SOCKETS
 
700
                int num_events;
 
701
                const apr_pollfd_t *pdesc;
 
702
 
 
703
                rv = apr_pollset_poll(ctx->pollset, f->r->server->timeout,
 
704
                                      &num_events, &pdesc);
 
705
                if (rv || dc->debug >= DBGLVL_GORY) {
 
706
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
 
707
                                  rv, f->r, "apr_pollset_poll()");
 
708
                }
 
709
                if (rv != APR_SUCCESS && !APR_STATUS_IS_EINTR(rv)) {
 
710
                    /* some error such as APR_TIMEUP */
 
711
                    return rv;
 
712
                }
 
713
#else /* APR_FILES_AS_SOCKETS */
 
714
                /* Yuck... I'd really like to wait until I can read
 
715
                 * or write, but instead I have to sleep and try again
 
716
                 */
 
717
                apr_sleep(100000); /* 100 milliseconds */
 
718
                if (dc->debug >= DBGLVL_GORY) {
 
719
                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
 
720
                                  0, f->r, "apr_sleep()");
 
721
                }
 
722
#endif /* APR_FILES_AS_SOCKETS */
 
723
            }
 
724
            else if (rv != APR_SUCCESS) {
 
725
                return rv;
 
726
            }
 
727
        }
 
728
    } while (bytes_written < len);
 
729
    return rv;
 
730
}
 
731
 
 
732
/* ef_unified_filter:
 
733
 *
 
734
 * runs the bucket brigade bb through the filter and puts the result into
 
735
 * bb, dropping the previous content of bb (the input)
 
736
 */
 
737
 
 
738
static int ef_unified_filter(ap_filter_t *f, apr_bucket_brigade *bb)
 
739
{
 
740
    request_rec *r = f->r;
 
741
    conn_rec *c = r->connection;
 
742
    ef_ctx_t *ctx = f->ctx;
 
743
    apr_bucket *b;
 
744
    ef_dir_t *dc;
 
745
    apr_size_t len;
 
746
    const char *data;
 
747
    apr_status_t rv;
 
748
    char buf[4096];
 
749
    apr_bucket *eos = NULL;
 
750
    apr_bucket_brigade *bb_tmp;
 
751
 
 
752
    dc = ctx->dc;
 
753
    bb_tmp = apr_brigade_create(r->pool, c->bucket_alloc);
 
754
 
 
755
    for (b = APR_BRIGADE_FIRST(bb);
 
756
         b != APR_BRIGADE_SENTINEL(bb);
 
757
         b = APR_BUCKET_NEXT(b))
 
758
    {
 
759
        if (APR_BUCKET_IS_EOS(b)) {
 
760
            eos = b;
 
761
            break;
 
762
        }
 
763
 
 
764
        rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
 
765
        if (rv != APR_SUCCESS) {
 
766
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "apr_bucket_read()");
 
767
            return rv;
 
768
        }
 
769
 
 
770
        /* Good cast, we just tested len isn't negative */
 
771
        if (len > 0 &&
 
772
            (rv = pass_data_to_filter(f, data, (apr_size_t)len, bb_tmp))
 
773
                != APR_SUCCESS) {
 
774
            return rv;
 
775
        }
 
776
    }
 
777
 
 
778
    apr_brigade_cleanup(bb);
 
779
    APR_BRIGADE_CONCAT(bb, bb_tmp);
 
780
    apr_brigade_destroy(bb_tmp);
 
781
 
 
782
    if (eos) {
 
783
        /* close the child's stdin to signal that no more data is coming;
 
784
         * that will cause the child to finish generating output
 
785
         */
 
786
        if ((rv = apr_file_close(ctx->proc->in)) != APR_SUCCESS) {
 
787
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 
788
                          "apr_file_close(child input)");
 
789
            return rv;
 
790
        }
 
791
        /* since we've seen eos and closed the child's stdin, set the proper pipe
 
792
         * timeout; we don't care if we don't return from apr_file_read() for a while...
 
793
         */
 
794
        rv = apr_file_pipe_timeout_set(ctx->proc->out,
 
795
                                       r->server->timeout);
 
796
        if (rv) {
 
797
            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 
798
                          "apr_file_pipe_timeout_set(child output)");
 
799
            return rv;
 
800
        }
 
801
    }
 
802
 
 
803
    do {
 
804
        len = sizeof(buf);
 
805
        rv = apr_file_read(ctx->proc->out,
 
806
                      buf,
 
807
                      &len);
 
808
        if ((rv && !APR_STATUS_IS_EOF(rv) && !APR_STATUS_IS_EAGAIN(rv)) ||
 
809
            dc->debug >= DBGLVL_GORY) {
 
810
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
 
811
                          "apr_file_read(child output), len %" APR_SIZE_T_FMT,
 
812
                          !rv ? len : -1);
 
813
        }
 
814
        if (APR_STATUS_IS_EAGAIN(rv)) {
 
815
            if (eos) {
 
816
                /* should not occur, because we have an APR timeout in place */
 
817
                AP_DEBUG_ASSERT(1 != 1);
 
818
            }
 
819
            return APR_SUCCESS;
 
820
        }
 
821
 
 
822
        if (rv == APR_SUCCESS) {
 
823
            b = apr_bucket_heap_create(buf, len, NULL, c->bucket_alloc);
 
824
            APR_BRIGADE_INSERT_TAIL(bb, b);
 
825
        }
 
826
    } while (rv == APR_SUCCESS);
 
827
 
 
828
    if (!APR_STATUS_IS_EOF(rv)) {
 
829
        return rv;
 
830
    }
 
831
 
 
832
    if (eos) {
 
833
        b = apr_bucket_eos_create(c->bucket_alloc);
 
834
        APR_BRIGADE_INSERT_TAIL(bb, b);
 
835
    }
 
836
 
 
837
    return APR_SUCCESS;
 
838
}
 
839
 
 
840
static apr_status_t ef_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
 
841
{
 
842
    request_rec *r = f->r;
 
843
    ef_ctx_t *ctx = f->ctx;
 
844
    apr_status_t rv;
 
845
 
 
846
    if (!ctx) {
 
847
        if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
 
848
            return rv;
 
849
        }
 
850
        ctx = f->ctx;
 
851
    }
 
852
    if (ctx->noop) {
 
853
        ap_remove_output_filter(f);
 
854
        return ap_pass_brigade(f->next, bb);
 
855
    }
 
856
 
 
857
    rv = ef_unified_filter(f, bb);
 
858
    if (rv != APR_SUCCESS) {
 
859
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 
860
                      "ef_unified_filter() failed");
 
861
    }
 
862
 
 
863
    if ((rv = ap_pass_brigade(f->next, bb)) != APR_SUCCESS) {
 
864
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
 
865
                      "ap_pass_brigade() failed");
 
866
    }
 
867
    return rv;
 
868
}
 
869
 
 
870
static int ef_input_filter(ap_filter_t *f, apr_bucket_brigade *bb,
 
871
                           ap_input_mode_t mode, apr_read_type_e block,
 
872
                           apr_off_t readbytes)
 
873
{
 
874
    ef_ctx_t *ctx = f->ctx;
 
875
    apr_status_t rv;
 
876
 
 
877
    if (!ctx) {
 
878
        if ((rv = init_filter_instance(f)) != APR_SUCCESS) {
 
879
            return rv;
 
880
        }
 
881
        ctx = f->ctx;
 
882
    }
 
883
 
 
884
    if (ctx->noop) {
 
885
        ap_remove_input_filter(f);
 
886
        return ap_get_brigade(f->next, bb, mode, block, readbytes);
 
887
    }
 
888
 
 
889
    rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
 
890
    if (rv != APR_SUCCESS) {
 
891
        return rv;
 
892
    }
 
893
 
 
894
    rv = ef_unified_filter(f, bb);
 
895
    return rv;
 
896
}
 
897
 
 
898
module AP_MODULE_DECLARE_DATA ext_filter_module =
 
899
{
 
900
    STANDARD20_MODULE_STUFF,
 
901
    create_ef_dir_conf,
 
902
    merge_ef_dir_conf,
 
903
    create_ef_server_conf,
 
904
    NULL,
 
905
    cmds,
 
906
    register_hooks
 
907
};