Reverse the order of the test... if clang, then use clang, because some clangs also...
[apache-httpd.git] / modules / filters / mod_xml2enc.c
1 /*      Copyright (c) 2007-11, WebThing Ltd
2  *      Copyright (c) 2011-, The Apache Software Foundation
3  *
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
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  */
19
20 #if defined(WIN32)
21 #define XML2ENC_DECLARE_EXPORT
22 #endif
23
24 #include <ctype.h>
25
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"
34 #endif
35 #endif
36
37 /* libxml2 */
38 #include <libxml/encoding.h>
39
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
45 #endif
46 #endif
47
48 #include "http_protocol.h"
49 #include "http_config.h"
50 #include "http_log.h"
51 #include "apr_strings.h"
52 #include "apr_xlate.h"
53
54 #include "apr_optional.h"
55 #include "mod_xml2enc.h"
56
57 module AP_MODULE_DECLARE_DATA xml2enc_module;
58
59 #define BUFLEN 8192
60 #define BUF_MIN 4096
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))
64
65 #define ENC_INITIALISED 0x100
66 #define ENC_SEEN_EOS 0x200
67 #define ENC_SKIPTO ENCIO_SKIPTO
68
69 #define HAVE_ENCODING(enc) \
70         (((enc)!=XML_CHAR_ENCODING_NONE)&&((enc)!=XML_CHAR_ENCODING_ERROR))
71
72 /*
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
75  * XXX: handling.
76  */
77 typedef struct {
78     xmlCharEncoding xml2enc;
79     char* buf;
80     apr_size_t bytes;
81     apr_xlate_t* convset;
82     unsigned int flags;
83     apr_off_t bblen;
84     apr_bucket_brigade* bbnext;
85     apr_bucket_brigade* bbsave;
86     const char* encoding;
87 } xml2ctx;
88
89 typedef struct {
90     const char* default_charset;
91     xmlCharEncoding default_encoding;
92     apr_array_header_t* skipto;
93 } xml2cfg;
94
95 typedef struct {
96     const char* val;
97 } tattr;
98
99 static ap_regex_t* seek_meta_ctype;
100 static ap_regex_t* seek_charset;
101
102 static apr_status_t xml2enc_filter(request_rec* r, const char* enc,
103                                    unsigned int mode)
104 {
105     /* set up a ready-initialised ctx to convert to enc, and insert filter */
106     apr_xlate_t* convset; 
107     apr_status_t rv;
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;
112     }
113     else if ((mode & ENCIO) == ENCIO_INPUT) {
114         rv = apr_xlate_open(&convset, "UTF-8", enc, r->pool);
115         flags |= ENC_INITIALISED;
116     }
117     else if ((mode & ENCIO) == ENCIO_INPUT_CHECKS) {
118         convset = NULL;
119         rv = APR_SUCCESS; /* we'll initialise later by sniffing */
120     }
121     else {
122         rv = APR_EGENERAL;
123         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01426)
124                       "xml2enc: bad mode %x", mode);
125     }
126     if (rv == APR_SUCCESS) {
127         xml2ctx* ctx = apr_pcalloc(r->pool, sizeof(xml2ctx));
128         ctx->flags = flags;
129         if (flags & ENC_INITIALISED) {
130             ctx->convset = convset;
131             ctx->bblen = BUFLEN;
132             ctx->buf = apr_palloc(r->pool, (apr_size_t)ctx->bblen);
133         }
134         ap_add_output_filter("xml2enc", ctx, r, r->connection);
135     }
136     else {
137         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01427)
138                       "xml2enc: Charset %s not supported.", enc) ;
139     }
140     return rv;
141 }
142
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)
146 {
147     apr_status_t rv;
148     xml2cfg* cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
149     if ((cfg->skipto != NULL) && (ctx->flags & ENC_SKIPTO)) {
150         int found = 0;
151         char* p = ap_strchr(ctx->buf, '<');
152         tattr* starts = (tattr*) cfg->skipto->elts;
153         while (!found && p && *p) {
154             int i;
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. */
158                     apr_bucket* b;
159                     apr_bucket* bstart;
160                     rv = apr_brigade_partition(ctx->bbsave, (p-ctx->buf),
161                                                &bstart);
162                     ap_assert(rv == APR_SUCCESS);
163                     while (b = APR_BRIGADE_FIRST(ctx->bbsave), b != bstart) {
164                         apr_bucket_delete(b);
165                     }
166                     ctx->bytes -= (p-ctx->buf);
167                     ctx->buf = p ;
168                     found = 1;
169                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01428)
170                                   "Skipped to first <%s> element",
171                                   starts[i].val) ;
172                     break;
173                 }
174             }
175             p = ap_strchr(p+1, '<');
176         }
177         if (p == NULL) {
178             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01429)
179                           "Failed to find start of recognised HTML!");
180         }
181     }
182 }
183 static void sniff_encoding(request_rec* r, xml2ctx* ctx)
184 {
185     xml2cfg* cfg = NULL; /* initialise to shut compiler warnings up */
186     char* p ;
187     apr_bucket* cutb;
188     apr_bucket* cute;
189     apr_bucket* b;
190     ap_regmatch_t match[2] ;
191     apr_status_t rv;
192     const char* ctype = r->content_type;
193
194     if (ctype) {
195         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01430)
196                       "Content-Type is %s", ctype) ;
197
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)) {
200             p += 8 ;
201             if (ctx->encoding = apr_pstrndup(r->pool, p, strcspn(p, " ;") ),
202                 ctx->encoding) {
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);
206             }
207         }
208     }
209   
210     /* to sniff, first we look for BOM */
211     if (ctx->xml2enc == XML_CHAR_ENCODING_NONE) {
212         ctx->xml2enc = xmlDetectCharEncoding((const xmlChar*)ctx->buf,
213                                              ctx->bytes); 
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);
218         }
219     }
220
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),
237                     ctx->encoding) {
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) ;
242                 }
243             }
244         }
245
246         /* cut out the <meta> we're invalidating */
247         while (cutb != cute) {
248             b = APR_BUCKET_NEXT(cutb);
249             apr_bucket_delete(cutb);
250             cutb = b;
251         }
252         /* and leave a string */
253         ctx->buf[ctx->bytes] = 0;
254     }
255
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.
259      *
260      * No, that means no-metafix breaks things.  Deal immediately with
261      * this particular instance of metafix.
262      */
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);
268         }
269     }
270     /* Test again: this fallback is only appropriate if we couldn't
271      * just fall back to the configuration default.
272      */
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",
277                       ctx->encoding);
278         if (apr_xlate_open(&ctx->convset, "UTF-8", ctx->encoding, r->pool)
279             == APR_SUCCESS) {
280             ctx->xml2enc = XML_CHAR_ENCODING_UTF8 ;
281         } else {
282             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01435)
283                           "Charset %s not supported.  Consider aliasing it?",
284                           ctx->encoding) ;
285         }
286     }
287
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 ;
294     }
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",
298                                           NULL);
299         } else {
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;
306         }
307     }
308 }
309
310 static apr_status_t xml2enc_filter_init(ap_filter_t* f)
311 {
312     xml2ctx* ctx;
313     if (!f->ctx) {
314         xml2cfg* cfg = ap_get_module_config(f->r->per_dir_config,
315                                             &xml2enc_module);
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;
320         }
321     }
322     return APR_SUCCESS;
323 }
324 static apr_status_t xml2enc_ffunc(ap_filter_t* f, apr_bucket_brigade* bb)
325 {
326     xml2ctx* ctx = f->ctx;
327     apr_status_t rv;
328     apr_bucket* b;
329     apr_bucket* bstart;
330     apr_size_t insz = 0;
331     int pending_meta = 0;
332     char *ctype;
333     char *p;
334
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) ;
339     }
340
341     ctype = apr_pstrdup(f->r->pool, f->r->content_type);
342     for (p = ctype; *p; ++p)
343         if (isupper(*p))
344             *p = tolower(*p);
345
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) ;
350     }
351
352     if (ctx->bbsave == NULL) {
353         ctx->bbsave = apr_brigade_create(f->r->pool,
354                                          f->r->connection->bucket_alloc);
355     }
356     /* append to any data left over from last time */
357     APR_BRIGADE_CONCAT(ctx->bbsave, bb);
358
359     if (!(ctx->flags & ENC_INITIALISED)) {
360         /* some kind of initialisation required */
361         /* Turn all this off when post-processing */
362
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;
369                     break;
370                 }
371             }
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);
377                 }
378                 return APR_SUCCESS;
379             }
380         }
381         if (ctx->bblen == -1) {
382             ctx->bblen = BUFLEN-1;
383         }
384
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);
392
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);
396
397         /* we might change the Content-Length, so let's force its re-calculation */
398         apr_table_unset(f->r->headers_out, "Content-Length");
399
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);
404     }
405     if (ctx->bbnext == NULL) {
406         ctx->bbnext = apr_brigade_create(f->r->pool,
407                                          f->r->connection->bucket_alloc);
408     }
409
410     if (!ctx->convset) {
411         rv = ap_pass_brigade(f->next, ctx->bbsave);
412         apr_brigade_cleanup(ctx->bbsave);
413         ap_remove_output_filter(f);
414         return rv;
415     }
416     /* move the data back to bb */
417     APR_BRIGADE_CONCAT(bb, ctx->bbsave);
418
419     while (!APR_BRIGADE_EMPTY(bb)) {
420         b = APR_BRIGADE_FIRST(bb);
421         ctx->bytes = 0;
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.
427              */
428             pending_meta = 1;
429             if (APR_BUCKET_IS_EOS(b)) {
430                 ap_remove_output_filter(f);
431                 APR_BRIGADE_CONCAT(ctx->bbnext, bb);
432             }
433             else if (!APR_BUCKET_IS_FLUSH(b)) {
434                 continue;
435             }
436         }
437         if (pending_meta) {
438             pending_meta = 0;
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) {
443                 return rv;
444             }
445             continue;
446         }
447         /* data bucket */
448         {
449             char* buf;
450             apr_size_t bytes = 0;
451             char fixbuf[BUFLEN];
452             apr_bucket* bdestroy = NULL;
453             if (insz > 0) { /* we have dangling data.  Flatten it. */
454                 buf = fixbuf;
455                 bytes = BUFLEN;
456                 rv = apr_brigade_flatten(bb, buf, &bytes);
457                 ap_assert(rv == APR_SUCCESS);
458                 if (bytes == insz) {
459                     /* this is only what we've already tried to convert.
460                      * The brigade is exhausted.
461                      * Save remaining data for next time round
462                      */
463           
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)
471                                   == APR_SUCCESS);
472                     }
473                     return rv;
474                 }
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);
479                 }
480                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01438)
481                               "xml2enc: consuming %" APR_SIZE_T_FMT
482                               " bytes flattened", bytes);
483             }
484             else {
485                 rv = apr_bucket_read(b, (const char**)&buf, &bytes,
486                                      APR_BLOCK_READ);
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);
492             }
493             /* OK, we've got some input we can use in [buf,bytes] */
494             if (rv == APR_SUCCESS) {
495                 apr_size_t consumed;
496                 xml2enc_run_preprocess(f, &buf, &bytes);
497                 consumed = insz = bytes;
498                 while (insz > 0) {
499                     apr_status_t rv2;
500                     if (ctx->bytes == ctx->bblen) {
501                         /* nothing was converted last time!
502                          * break out of this loop! 
503                          */
504                         b = apr_bucket_transient_create(buf+(bytes - insz), insz,
505                                                         bb->bucket_alloc);
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);
510                         break;
511                     }
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);
519                     consumed = insz;
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)
524                                       "ap_fwrite failed");
525                         return rv2;
526                     }
527                     switch (rv) {
528                     case APR_SUCCESS:
529                         continue;
530                     case APR_EINCOMPLETE:
531                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01443)
532                                       "INCOMPLETE");
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
536                                        */
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!");
540                         --insz;
541                         continue;
542                     default:
543                         /* Erk!  What's this?
544                          * Bail out, flush, and hope to eat the buf raw
545                          */
546                         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01445)
547                                       "Failed to convert input; trying it raw") ;
548                         ctx->convset = NULL;
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)
552                                           "ap_fflush failed");
553                         apr_brigade_cleanup(ctx->bbnext);
554                     }
555                 }
556             } else {
557                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01447)
558                               "xml2enc: error reading data") ;
559             }
560             if (bdestroy)
561                 apr_bucket_destroy(bdestroy);
562             if (rv != APR_SUCCESS)
563                 return rv;
564         }
565     }
566     if (pending_meta) {
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) {
571             return rv;
572         }
573     }
574
575     return APR_SUCCESS;
576 }
577
578 static apr_status_t xml2enc_charset(request_rec* r, xmlCharEncoding* encp,
579                                     const char** encoding)
580 {
581     xml2ctx* ctx = ap_get_module_config(r->request_config, &xml2enc_module);
582     if (!ctx || !(ctx->flags & ENC_INITIALISED)) {
583         return APR_EAGAIN;
584     }
585     *encp = ctx->xml2enc;
586     *encoding = ctx->encoding;
587     return HAVE_ENCODING(ctx->xml2enc) ? APR_SUCCESS : APR_EGENERAL;
588 }
589
590 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
591 static void xml2enc_hooks(apr_pool_t* pool)
592 {
593     ap_register_output_filter_protocol("xml2enc", xml2enc_ffunc,
594                                        xml2enc_filter_init,
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) ;
603 }
604 static const char* set_alias(cmd_parms* cmd, void* CFG,
605                              const char* charset, const char* alias)
606 {
607     const char* errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
608     if (errmsg != NULL)
609         return errmsg ;
610     else if (xmlAddEncodingAlias(charset, alias) == 0)
611         return NULL;
612     else
613         return "Error setting charset alias";
614 }
615
616 static const char* set_default(cmd_parms* cmd, void* CFG, const char* charset)
617 {
618     xml2cfg* cfg = CFG;
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";
626     default:
627         return NULL;
628     }
629 }
630 static const char* set_skipto(cmd_parms* cmd, void* CFG, const char* arg)
631 {
632     tattr* attr;
633     xml2cfg* cfg = CFG;
634     if (cfg->skipto == NULL)
635         cfg->skipto = apr_array_make(cmd->pool, 4, sizeof(tattr));
636     attr = apr_array_push(cfg->skipto) ;
637     attr->val = arg;
638     return NULL;
639 }
640
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"),
648     { NULL }
649 };
650 static void* xml2enc_config(apr_pool_t* pool, char* x)
651 {
652     xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
653     ret->default_encoding = XML_CHAR_ENCODING_NONE ;
654     return ret;
655 }
656
657 static void* xml2enc_merge(apr_pool_t* pool, void* BASE, void* ADD)
658 {
659     xml2cfg* base = BASE;
660     xml2cfg* add = ADD;
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;
667     return ret;
668 }
669
670 AP_DECLARE_MODULE(xml2enc) = {
671     STANDARD20_MODULE_STUFF,
672     xml2enc_config,
673     xml2enc_merge,
674     NULL,
675     NULL,
676     xml2enc_cmds,
677     xml2enc_hooks
678 };
679
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)