1 /* Copyright (c) 2007-11, WebThing Ltd
2 * Copyright (c) 2011-, The Apache Software Foundation
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
21 #define XML2ENC_DECLARE_EXPORT
26 /* libxml2 includes unicode/[...].h files which uses C++ comments */
27 #if defined(__clang__)
28 #pragma clang diagnostic push
29 #pragma clang diagnostic warning "-Wcomment"
30 #elif defined(__GNUC__)
31 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
32 #pragma GCC diagnostic push
33 #pragma GCC diagnostic warning "-Wcomment"
38 #include <libxml/encoding.h>
40 #if defined(__clang__)
41 #pragma clang diagnostic pop
42 #elif defined(__GNUC__)
43 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
44 #pragma GCC diagnostic pop
48 #include "http_protocol.h"
49 #include "http_config.h"
51 #include "apr_strings.h"
52 #include "apr_xlate.h"
54 #include "apr_optional.h"
55 #include "mod_xml2enc.h"
57 module AP_MODULE_DECLARE_DATA xml2enc_module;
61 #define APR_BRIGADE_DO(b,bb) for (b = APR_BRIGADE_FIRST(bb); \
62 b != APR_BRIGADE_SENTINEL(bb); \
63 b = APR_BUCKET_NEXT(b))
65 #define ENC_INITIALISED 0x100
66 #define ENC_SEEN_EOS 0x200
67 #define ENC_SKIPTO ENCIO_SKIPTO
69 #define HAVE_ENCODING(enc) \
70 (((enc)!=XML_CHAR_ENCODING_NONE)&&((enc)!=XML_CHAR_ENCODING_ERROR))
73 * XXX: Check all those ap_assert()s ans replace those that should not happen
74 * XXX: with AP_DEBUG_ASSERT and those that may happen with proper error
78 xmlCharEncoding xml2enc;
84 apr_bucket_brigade* bbnext;
85 apr_bucket_brigade* bbsave;
90 const char* default_charset;
91 xmlCharEncoding default_encoding;
92 apr_array_header_t* skipto;
99 static ap_regex_t* seek_meta_ctype;
100 static ap_regex_t* seek_charset;
102 static apr_status_t xml2enc_filter(request_rec* r, const char* enc,
105 /* set up a ready-initialised ctx to convert to enc, and insert filter */
106 apr_xlate_t* convset;
108 unsigned int flags = (mode ^ ENCIO);
109 if ((mode & ENCIO) == ENCIO_OUTPUT) {
110 rv = apr_xlate_open(&convset, enc, "UTF-8", r->pool);
111 flags |= ENC_INITIALISED;
113 else if ((mode & ENCIO) == ENCIO_INPUT) {
114 rv = apr_xlate_open(&convset, "UTF-8", enc, r->pool);
115 flags |= ENC_INITIALISED;
117 else if ((mode & ENCIO) == ENCIO_INPUT_CHECKS) {
119 rv = APR_SUCCESS; /* we'll initialise later by sniffing */
123 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01426)
124 "xml2enc: bad mode %x", mode);
126 if (rv == APR_SUCCESS) {
127 xml2ctx* ctx = apr_pcalloc(r->pool, sizeof(xml2ctx));
129 if (flags & ENC_INITIALISED) {
130 ctx->convset = convset;
132 ctx->buf = apr_palloc(r->pool, (apr_size_t)ctx->bblen);
134 ap_add_output_filter("xml2enc", ctx, r, r->connection);
137 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01427)
138 "xml2enc: Charset %s not supported.", enc) ;
143 /* This needs to operate only when we're using htmlParser */
144 /* Different modules may apply different rules here. Ho, hum. */
145 static void fix_skipto(request_rec* r, xml2ctx* ctx)
148 xml2cfg* cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
149 if ((cfg->skipto != NULL) && (ctx->flags & ENC_SKIPTO)) {
151 char* p = ap_strchr(ctx->buf, '<');
152 tattr* starts = (tattr*) cfg->skipto->elts;
153 while (!found && p && *p) {
155 for (i = 0; i < cfg->skipto->nelts; ++i) {
156 if (!strncasecmp(p+1, starts[i].val, strlen(starts[i].val))) {
157 /* found a starting element. Strip all that comes before. */
160 rv = apr_brigade_partition(ctx->bbsave, (p-ctx->buf),
162 ap_assert(rv == APR_SUCCESS);
163 while (b = APR_BRIGADE_FIRST(ctx->bbsave), b != bstart) {
164 apr_bucket_delete(b);
166 ctx->bytes -= (p-ctx->buf);
169 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01428)
170 "Skipped to first <%s> element",
175 p = ap_strchr(p+1, '<');
178 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01429)
179 "Failed to find start of recognised HTML!");
183 static void sniff_encoding(request_rec* r, xml2ctx* ctx)
185 xml2cfg* cfg = NULL; /* initialise to shut compiler warnings up */
190 ap_regmatch_t match[2] ;
192 const char* ctype = r->content_type;
195 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01430)
196 "Content-Type is %s", ctype) ;
198 /* If we've got it in the HTTP headers, there's nothing to do */
199 if (ctype && (p = ap_strcasestr(ctype, "charset=") , p != NULL)) {
201 if (ctx->encoding = apr_pstrndup(r->pool, p, strcspn(p, " ;") ),
203 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01431)
204 "Got charset %s from HTTP headers", ctx->encoding) ;
205 ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
210 /* to sniff, first we look for BOM */
211 if (ctx->xml2enc == XML_CHAR_ENCODING_NONE) {
212 ctx->xml2enc = xmlDetectCharEncoding((const xmlChar*)ctx->buf,
214 if (HAVE_ENCODING(ctx->xml2enc)) {
215 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01432)
216 "Got charset from XML rules.") ;
217 ctx->encoding = xmlGetCharEncodingName(ctx->xml2enc);
221 /* If none of the above, look for a META-thingey */
222 /* also we're probably about to invalidate it, so we remove it. */
223 if (ap_regexec(seek_meta_ctype, ctx->buf, 1, match, 0) == 0 ) {
224 /* get markers on the start and end of the match */
225 rv = apr_brigade_partition(ctx->bbsave, match[0].rm_eo, &cute);
226 ap_assert(rv == APR_SUCCESS);
227 rv = apr_brigade_partition(ctx->bbsave, match[0].rm_so, &cutb);
228 ap_assert(rv == APR_SUCCESS);
229 /* now set length of useful buf for start-of-data hooks */
230 ctx->bytes = match[0].rm_so;
231 if (ctx->encoding == NULL) {
232 p = apr_pstrndup(r->pool, ctx->buf + match[0].rm_so,
233 match[0].rm_eo - match[0].rm_so) ;
234 if (ap_regexec(seek_charset, p, 2, match, 0) == 0) {
235 if (ctx->encoding = apr_pstrndup(r->pool, p+match[1].rm_so,
236 match[1].rm_eo - match[1].rm_so),
238 ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
239 if (HAVE_ENCODING(ctx->xml2enc))
240 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01433)
241 "Got charset %s from HTML META", ctx->encoding) ;
246 /* cut out the <meta> we're invalidating */
247 while (cutb != cute) {
248 b = APR_BUCKET_NEXT(cutb);
249 apr_bucket_delete(cutb);
252 /* and leave a string */
253 ctx->buf[ctx->bytes] = 0;
256 /* either it's set to something we found or it's still the default */
257 /* Aaargh! libxml2 has undocumented <META-crap> support. So this fails
258 * if metafix is not active. Have to make it conditional.
260 * No, that means no-metafix breaks things. Deal immediately with
261 * this particular instance of metafix.
263 if (!HAVE_ENCODING(ctx->xml2enc)) {
264 cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
265 if (!ctx->encoding) {
266 ctx->encoding = cfg->default_charset?cfg->default_charset:"ISO-8859-1";
267 ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
270 /* Test again: this fallback is only appropriate if we couldn't
271 * just fall back to the configuration default.
273 if (!HAVE_ENCODING(ctx->xml2enc)) {
274 /* Unsupported charset. Can we get (iconv) support through apr_xlate? */
275 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01434)
276 "Charset %s not supported by libxml2; trying apr_xlate",
278 if (apr_xlate_open(&ctx->convset, "UTF-8", ctx->encoding, r->pool)
280 ctx->xml2enc = XML_CHAR_ENCODING_UTF8 ;
282 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01435)
283 "Charset %s not supported. Consider aliasing it?",
288 if (!HAVE_ENCODING(ctx->xml2enc)) {
289 /* Use configuration default as a last resort */
290 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01436)
291 "No usable charset information; using configuration default");
292 ctx->xml2enc = (cfg->default_encoding == XML_CHAR_ENCODING_NONE)
293 ? XML_CHAR_ENCODING_8859_1 : cfg->default_encoding ;
295 if (ctype && ctx->encoding) {
296 if (ap_regexec(seek_charset, ctype, 2, match, 0)) {
297 r->content_type = apr_pstrcat(r->pool, ctype, ";charset=utf-8",
300 char* str = apr_palloc(r->pool, strlen(r->content_type) + 13
301 - (match[0].rm_eo - match[0].rm_so) + 1);
302 memcpy(str, r->content_type, match[1].rm_so);
303 memcpy(str + match[1].rm_so, "utf-8", 5);
304 strcpy(str + match[1].rm_so + 5, r->content_type+match[1].rm_eo);
305 r->content_type = str;
310 static apr_status_t xml2enc_filter_init(ap_filter_t* f)
314 xml2cfg* cfg = ap_get_module_config(f->r->per_dir_config,
316 f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(xml2ctx));
317 ctx->xml2enc = XML_CHAR_ENCODING_NONE;
318 if (cfg->skipto != NULL) {
319 ctx->flags |= ENC_SKIPTO;
324 static apr_status_t xml2enc_ffunc(ap_filter_t* f, apr_bucket_brigade* bb)
326 xml2ctx* ctx = f->ctx;
331 int pending_meta = 0;
335 if (!ctx || !f->r->content_type) {
336 /* log error about configuring this */
337 ap_remove_output_filter(f);
338 return ap_pass_brigade(f->next, bb) ;
341 ctype = apr_pstrdup(f->r->pool, f->r->content_type);
342 for (p = ctype; *p; ++p)
346 /* only act if starts-with "text/" or contains "xml" */
347 if (strncmp(ctype, "text/", 5) && !strstr(ctype, "xml")) {
348 ap_remove_output_filter(f);
349 return ap_pass_brigade(f->next, bb) ;
352 if (ctx->bbsave == NULL) {
353 ctx->bbsave = apr_brigade_create(f->r->pool,
354 f->r->connection->bucket_alloc);
356 /* append to any data left over from last time */
357 APR_BRIGADE_CONCAT(ctx->bbsave, bb);
359 if (!(ctx->flags & ENC_INITIALISED)) {
360 /* some kind of initialisation required */
361 /* Turn all this off when post-processing */
363 /* if we don't have enough data to sniff but more's to come, wait */
364 apr_brigade_length(ctx->bbsave, 0, &ctx->bblen);
365 if ((ctx->bblen < BUF_MIN) && (ctx->bblen != -1)) {
366 APR_BRIGADE_DO(b, ctx->bbsave) {
367 if (APR_BUCKET_IS_EOS(b)) {
368 ctx->flags |= ENC_SEEN_EOS;
372 if (!(ctx->flags & ENC_SEEN_EOS)) {
373 /* not enough data to sniff. Wait for more */
374 APR_BRIGADE_DO(b, ctx->bbsave) {
375 rv = apr_bucket_setaside(b, f->r->pool);
376 ap_assert(rv == APR_SUCCESS);
381 if (ctx->bblen == -1) {
382 ctx->bblen = BUFLEN-1;
385 /* flatten it into a NULL-terminated string */
386 ctx->buf = apr_palloc(f->r->pool, (apr_size_t)(ctx->bblen+1));
387 ctx->bytes = (apr_size_t)ctx->bblen;
388 rv = apr_brigade_flatten(ctx->bbsave, ctx->buf, &ctx->bytes);
389 ap_assert(rv == APR_SUCCESS);
390 ctx->buf[ctx->bytes] = 0;
391 sniff_encoding(f->r, ctx);
393 /* FIXME: hook here for rewriting start-of-data? */
394 /* nah, we only have one action here - call it inline */
395 fix_skipto(f->r, ctx);
397 /* we might change the Content-Length, so let's force its re-calculation */
398 apr_table_unset(f->r->headers_out, "Content-Length");
400 /* consume the data we just sniffed */
401 /* we need to omit any <meta> we just invalidated */
402 ctx->flags |= ENC_INITIALISED;
403 ap_set_module_config(f->r->request_config, &xml2enc_module, ctx);
405 if (ctx->bbnext == NULL) {
406 ctx->bbnext = apr_brigade_create(f->r->pool,
407 f->r->connection->bucket_alloc);
411 rv = ap_pass_brigade(f->next, ctx->bbsave);
412 apr_brigade_cleanup(ctx->bbsave);
413 ap_remove_output_filter(f);
416 /* move the data back to bb */
417 APR_BRIGADE_CONCAT(bb, ctx->bbsave);
419 while (!APR_BRIGADE_EMPTY(bb)) {
420 b = APR_BRIGADE_FIRST(bb);
422 if (APR_BUCKET_IS_METADATA(b)) {
423 APR_BUCKET_REMOVE(b);
424 APR_BRIGADE_INSERT_TAIL(ctx->bbnext, b);
425 /* Besides FLUSH, aggregate meta buckets to send them at
426 * once below. This resource filter is over on EOS.
429 if (APR_BUCKET_IS_EOS(b)) {
430 ap_remove_output_filter(f);
431 APR_BRIGADE_CONCAT(ctx->bbnext, bb);
433 else if (!APR_BUCKET_IS_FLUSH(b)) {
439 /* passing meta bucket down the chain */
440 rv = ap_pass_brigade(f->next, ctx->bbnext);
441 apr_brigade_cleanup(ctx->bbnext);
442 if (rv != APR_SUCCESS) {
450 apr_size_t bytes = 0;
452 apr_bucket* bdestroy = NULL;
453 if (insz > 0) { /* we have dangling data. Flatten it. */
456 rv = apr_brigade_flatten(bb, buf, &bytes);
457 ap_assert(rv == APR_SUCCESS);
459 /* this is only what we've already tried to convert.
460 * The brigade is exhausted.
461 * Save remaining data for next time round
464 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01437)
465 "xml2enc: Setting aside %" APR_SIZE_T_FMT
466 " unconverted bytes", bytes);
467 rv = ap_fflush(f->next, ctx->bbnext);
468 APR_BRIGADE_CONCAT(ctx->bbsave, bb);
469 APR_BRIGADE_DO(b, ctx->bbsave) {
470 ap_assert(apr_bucket_setaside(b, f->r->pool)
475 /* remove the data we've just read */
476 rv = apr_brigade_partition(bb, bytes, &bstart);
477 while (b = APR_BRIGADE_FIRST(bb), b != bstart) {
478 apr_bucket_delete(b);
480 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01438)
481 "xml2enc: consuming %" APR_SIZE_T_FMT
482 " bytes flattened", bytes);
485 rv = apr_bucket_read(b, (const char**)&buf, &bytes,
487 APR_BUCKET_REMOVE(b);
488 bdestroy = b; /* can't destroy until finished with the data */
489 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01439)
490 "xml2enc: consuming %" APR_SIZE_T_FMT
491 " bytes from bucket", bytes);
493 /* OK, we've got some input we can use in [buf,bytes] */
494 if (rv == APR_SUCCESS) {
496 xml2enc_run_preprocess(f, &buf, &bytes);
497 consumed = insz = bytes;
500 if (ctx->bytes == ctx->bblen) {
501 /* nothing was converted last time!
502 * break out of this loop!
504 b = apr_bucket_transient_create(buf+(bytes - insz), insz,
506 APR_BRIGADE_INSERT_HEAD(bb, b);
507 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01440)
508 "xml2enc: reinserting %" APR_SIZE_T_FMT
509 " unconsumed bytes from bucket", insz);
512 ctx->bytes = (apr_size_t)ctx->bblen;
513 rv = apr_xlate_conv_buffer(ctx->convset, buf+(bytes - insz),
514 &insz, ctx->buf, &ctx->bytes);
515 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(01441)
516 "xml2enc: converted %" APR_SIZE_T_FMT
517 "/%" APR_OFF_T_FMT " bytes", consumed - insz,
518 ctx->bblen - ctx->bytes);
520 rv2 = ap_fwrite(f->next, ctx->bbnext, ctx->buf,
521 (apr_size_t)ctx->bblen - ctx->bytes);
522 if (rv2 != APR_SUCCESS) {
523 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv2, f->r, APLOGNO(01442)
530 case APR_EINCOMPLETE:
531 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01443)
533 continue; /* If outbuf too small, go round again.
534 * If it was inbuf, we'll break out when
535 * we test ctx->bytes == ctx->bblen
537 case APR_EINVAL: /* try skipping one bad byte */
538 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01444)
539 "Skipping invalid byte(s) in input stream!");
544 * Bail out, flush, and hope to eat the buf raw
546 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01445)
547 "Failed to convert input; trying it raw") ;
549 rv = ap_fflush(f->next, ctx->bbnext);
550 if (rv != APR_SUCCESS)
551 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(01446)
553 apr_brigade_cleanup(ctx->bbnext);
557 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01447)
558 "xml2enc: error reading data") ;
561 apr_bucket_destroy(bdestroy);
562 if (rv != APR_SUCCESS)
567 /* passing pending meta bucket down the chain before leaving */
568 rv = ap_pass_brigade(f->next, ctx->bbnext);
569 apr_brigade_cleanup(ctx->bbnext);
570 if (rv != APR_SUCCESS) {
578 static apr_status_t xml2enc_charset(request_rec* r, xmlCharEncoding* encp,
579 const char** encoding)
581 xml2ctx* ctx = ap_get_module_config(r->request_config, &xml2enc_module);
582 if (!ctx || !(ctx->flags & ENC_INITIALISED)) {
585 *encp = ctx->xml2enc;
586 *encoding = ctx->encoding;
587 return HAVE_ENCODING(ctx->xml2enc) ? APR_SUCCESS : APR_EGENERAL;
590 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
591 static void xml2enc_hooks(apr_pool_t* pool)
593 ap_register_output_filter_protocol("xml2enc", xml2enc_ffunc,
595 AP_FTYPE_RESOURCE, PROTO_FLAGS);
596 APR_REGISTER_OPTIONAL_FN(xml2enc_filter);
597 APR_REGISTER_OPTIONAL_FN(xml2enc_charset);
598 seek_meta_ctype = ap_pregcomp(pool,
599 "(<meta[^>]*http-equiv[ \t\r\n='\"]*content-type[^>]*>)",
600 AP_REG_EXTENDED|AP_REG_ICASE) ;
601 seek_charset = ap_pregcomp(pool, "charset=([A-Za-z0-9_-]+)",
602 AP_REG_EXTENDED|AP_REG_ICASE) ;
604 static const char* set_alias(cmd_parms* cmd, void* CFG,
605 const char* charset, const char* alias)
607 const char* errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
610 else if (xmlAddEncodingAlias(charset, alias) == 0)
613 return "Error setting charset alias";
616 static const char* set_default(cmd_parms* cmd, void* CFG, const char* charset)
619 cfg->default_charset = charset;
620 cfg->default_encoding = xmlParseCharEncoding(charset);
621 switch(cfg->default_encoding) {
622 case XML_CHAR_ENCODING_NONE:
623 return "Default charset not found";
624 case XML_CHAR_ENCODING_ERROR:
625 return "Invalid or unsupported default charset";
630 static const char* set_skipto(cmd_parms* cmd, void* CFG, const char* arg)
634 if (cfg->skipto == NULL)
635 cfg->skipto = apr_array_make(cmd->pool, 4, sizeof(tattr));
636 attr = apr_array_push(cfg->skipto) ;
641 static const command_rec xml2enc_cmds[] = {
642 AP_INIT_TAKE1("xml2EncDefault", set_default, NULL, OR_ALL,
643 "Usage: xml2EncDefault charset"),
644 AP_INIT_ITERATE2("xml2EncAlias", set_alias, NULL, RSRC_CONF,
645 "EncodingAlias charset alias [more aliases]"),
646 AP_INIT_ITERATE("xml2StartParse", set_skipto, NULL, OR_ALL,
647 "Ignore anything in front of the first of these elements"),
650 static void* xml2enc_config(apr_pool_t* pool, char* x)
652 xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
653 ret->default_encoding = XML_CHAR_ENCODING_NONE ;
657 static void* xml2enc_merge(apr_pool_t* pool, void* BASE, void* ADD)
659 xml2cfg* base = BASE;
661 xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
662 ret->default_encoding = (add->default_encoding == XML_CHAR_ENCODING_NONE)
663 ? base->default_encoding : add->default_encoding ;
664 ret->default_charset = add->default_charset
665 ? add->default_charset : base->default_charset;
666 ret->skipto = add->skipto ? add->skipto : base->skipto;
670 AP_DECLARE_MODULE(xml2enc) = {
671 STANDARD20_MODULE_STUFF,
680 APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(xml2enc, XML2ENC, int, preprocess,
681 (ap_filter_t *f, char** bufp, apr_size_t* bytesp),
682 (f, bufp, bytesp), OK, DECLINED)