]> granicus.if.org Git - apache/blob - modules/filters/mod_xml2enc.c
check: merge warning fixes from feature branch
[apache] / 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 */
27 #include <libxml/encoding.h>
28
29 #include "http_protocol.h"
30 #include "http_config.h"
31 #include "http_log.h"
32 #include "apr_strings.h"
33 #include "apr_xlate.h"
34
35 #include "apr_optional.h"
36 #include "mod_xml2enc.h"
37
38 module AP_MODULE_DECLARE_DATA xml2enc_module;
39
40 #define BUFLEN 8192
41 #define BUF_MIN 4096
42 #define APR_BRIGADE_DO(b,bb) for (b = APR_BRIGADE_FIRST(bb); \
43                                   b != APR_BRIGADE_SENTINEL(bb); \
44                                   b = APR_BUCKET_NEXT(b))
45
46 #define ENC_INITIALISED 0x100
47 #define ENC_SEEN_EOS 0x200
48 #define ENC_SKIPTO ENCIO_SKIPTO
49
50 #define HAVE_ENCODING(enc) \
51         (((enc)!=XML_CHAR_ENCODING_NONE)&&((enc)!=XML_CHAR_ENCODING_ERROR))
52
53 /*
54  * XXX: Check all those ap_assert()s ans replace those that should not happen
55  * XXX: with AP_DEBUG_ASSERT and those that may happen with proper error
56  * XXX: handling.
57  */
58 typedef struct {
59     xmlCharEncoding xml2enc;
60     char* buf;
61     apr_size_t bytes;
62     apr_xlate_t* convset;
63     unsigned int flags;
64     apr_off_t bblen;
65     apr_bucket_brigade* bbnext;
66     apr_bucket_brigade* bbsave;
67     const char* encoding;
68 } xml2ctx;
69
70 typedef struct {
71     const char* default_charset;
72     xmlCharEncoding default_encoding;
73     apr_array_header_t* skipto;
74 } xml2cfg;
75
76 typedef struct {
77     const char* val;
78 } tattr;
79
80 static ap_regex_t* seek_meta_ctype;
81 static ap_regex_t* seek_charset;
82
83 static apr_status_t xml2enc_filter(request_rec* r, const char* enc,
84                                    unsigned int mode)
85 {
86     /* set up a ready-initialised ctx to convert to enc, and insert filter */
87     apr_xlate_t* convset; 
88     apr_status_t rv;
89     unsigned int flags = (mode ^ ENCIO);
90     if ((mode & ENCIO) == ENCIO_OUTPUT) {
91         rv = apr_xlate_open(&convset, enc, "UTF-8", r->pool);
92         flags |= ENC_INITIALISED;
93     }
94     else if ((mode & ENCIO) == ENCIO_INPUT) {
95         rv = apr_xlate_open(&convset, "UTF-8", enc, r->pool);
96         flags |= ENC_INITIALISED;
97     }
98     else if ((mode & ENCIO) == ENCIO_INPUT_CHECKS) {
99         convset = NULL;
100         rv = APR_SUCCESS; /* we'll initialise later by sniffing */
101     }
102     else {
103         rv = APR_EGENERAL;
104         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01426)
105                       "xml2enc: bad mode %x", mode);
106     }
107     if (rv == APR_SUCCESS) {
108         xml2ctx* ctx = apr_pcalloc(r->pool, sizeof(xml2ctx));
109         ctx->flags = flags;
110         if (flags & ENC_INITIALISED) {
111             ctx->convset = convset;
112             ctx->bblen = BUFLEN;
113             ctx->buf = apr_palloc(r->pool, (apr_size_t)ctx->bblen);
114         }
115         ap_add_output_filter("xml2enc", ctx, r, r->connection);
116     }
117     else {
118         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01427)
119                       "xml2enc: Charset %s not supported.", enc) ;
120     }
121     return rv;
122 }
123
124 /* This needs to operate only when we're using htmlParser */
125 /* Different modules may apply different rules here.  Ho, hum.  */
126 static void fix_skipto(request_rec* r, xml2ctx* ctx)
127 {
128     apr_status_t rv;
129     xml2cfg* cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
130     if ((cfg->skipto != NULL) && (ctx->flags & ENC_SKIPTO)) {
131         int found = 0;
132         char* p = ap_strchr(ctx->buf, '<');
133         tattr* starts = (tattr*) cfg->skipto->elts;
134         while (!found && p && *p) {
135             int i;
136             for (i = 0; i < cfg->skipto->nelts; ++i) {
137                 if (!strncasecmp(p+1, starts[i].val, strlen(starts[i].val))) {
138                     /* found a starting element. Strip all that comes before. */
139                     apr_bucket* b;
140                     apr_bucket* bstart;
141                     rv = apr_brigade_partition(ctx->bbsave, (p-ctx->buf),
142                                                &bstart);
143                     ap_assert(rv == APR_SUCCESS);
144                     while (b = APR_BRIGADE_FIRST(ctx->bbsave), b != bstart) {
145                         apr_bucket_delete(b);
146                     }
147                     ctx->bytes -= (p-ctx->buf);
148                     ctx->buf = p ;
149                     found = 1;
150                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01428)
151                                   "Skipped to first <%s> element",
152                                   starts[i].val) ;
153                     break;
154                 }
155             }
156             p = ap_strchr(p+1, '<');
157         }
158         if (p == NULL) {
159             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01429)
160                           "Failed to find start of recognised HTML!");
161         }
162     }
163 }
164 static void sniff_encoding(request_rec* r, xml2ctx* ctx)
165 {
166     xml2cfg* cfg = NULL; /* initialise to shut compiler warnings up */
167     char* p ;
168     apr_bucket* cutb;
169     apr_bucket* cute;
170     apr_bucket* b;
171     ap_regmatch_t match[2] ;
172     apr_status_t rv;
173     const char* ctype = r->content_type;
174
175     if (ctype) {
176         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01430)
177                       "Content-Type is %s", ctype) ;
178
179         /* If we've got it in the HTTP headers, there's nothing to do */
180         if (ctype && (p = ap_strcasestr(ctype, "charset=") , p != NULL)) {
181             p += 8 ;
182             if (ctx->encoding = apr_pstrndup(r->pool, p, strcspn(p, " ;") ),
183                 ctx->encoding) {
184                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01431)
185                               "Got charset %s from HTTP headers", ctx->encoding) ;
186                 ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
187             }
188         }
189     }
190   
191     /* to sniff, first we look for BOM */
192     if (ctx->xml2enc == XML_CHAR_ENCODING_NONE) {
193         ctx->xml2enc = xmlDetectCharEncoding((const xmlChar*)ctx->buf,
194                                              ctx->bytes); 
195         if (HAVE_ENCODING(ctx->xml2enc)) {
196             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01432)
197                           "Got charset from XML rules.") ;
198             ctx->encoding = xmlGetCharEncodingName(ctx->xml2enc);
199         }
200     }
201
202     /* If none of the above, look for a META-thingey */
203     /* also we're probably about to invalidate it, so we remove it. */
204     if (ap_regexec(seek_meta_ctype, ctx->buf, 1, match, 0) == 0 ) {
205         /* get markers on the start and end of the match */
206         rv = apr_brigade_partition(ctx->bbsave, match[0].rm_eo, &cute);
207         ap_assert(rv == APR_SUCCESS);
208         rv = apr_brigade_partition(ctx->bbsave, match[0].rm_so, &cutb);
209         ap_assert(rv == APR_SUCCESS);
210         /* now set length of useful buf for start-of-data hooks */
211         ctx->bytes = match[0].rm_so;
212         if (ctx->encoding == NULL) {
213             p = apr_pstrndup(r->pool, ctx->buf + match[0].rm_so,
214                              match[0].rm_eo - match[0].rm_so) ;
215             if (ap_regexec(seek_charset, p, 2, match, 0) == 0) {
216                 if (ctx->encoding = apr_pstrndup(r->pool, p+match[1].rm_so,
217                                                match[1].rm_eo - match[1].rm_so),
218                     ctx->encoding) {
219                     ctx->xml2enc = xmlParseCharEncoding(ctx->encoding);
220                     if (HAVE_ENCODING(ctx->xml2enc))
221                         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01433)
222                                       "Got charset %s from HTML META", ctx->encoding) ;
223                 }
224             }
225         }
226
227         /* cut out the <meta> we're invalidating */
228         while (cutb != cute) {
229             b = APR_BUCKET_NEXT(cutb);
230             apr_bucket_delete(cutb);
231             cutb = b;
232         }
233         /* and leave a string */
234         ctx->buf[ctx->bytes] = 0;
235     }
236
237     /* either it's set to something we found or it's still the default */
238     /* Aaargh!  libxml2 has undocumented <META-crap> support.  So this fails
239      * if metafix is not active.  Have to make it conditional.
240      *
241      * No, that means no-metafix breaks things.  Deal immediately with
242      * this particular instance of metafix.
243      */
244     if (!HAVE_ENCODING(ctx->xml2enc)) {
245         cfg = ap_get_module_config(r->per_dir_config, &xml2enc_module);
246         if (!ctx->encoding) {
247             ctx->encoding = cfg->default_charset?cfg->default_charset:"ISO-8859-1";
248         }
249         /* Unsupported charset. Can we get (iconv) support through apr_xlate? */
250         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01434)
251                       "Charset %s not supported by libxml2; trying apr_xlate",
252                       ctx->encoding);
253         if (apr_xlate_open(&ctx->convset, "UTF-8", ctx->encoding, r->pool)
254             == APR_SUCCESS) {
255             ctx->xml2enc = XML_CHAR_ENCODING_UTF8 ;
256         } else {
257             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01435)
258                           "Charset %s not supported.  Consider aliasing it?",
259                           ctx->encoding) ;
260         }
261     }
262
263     if (!HAVE_ENCODING(ctx->xml2enc)) {
264         /* Use configuration default as a last resort */
265         ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01436)
266                   "No usable charset information; using configuration default");
267         ctx->xml2enc = (cfg->default_encoding == XML_CHAR_ENCODING_NONE)
268                         ? XML_CHAR_ENCODING_8859_1 : cfg->default_encoding ;
269     }
270     if (ctype && ctx->encoding) {
271         if (ap_regexec(seek_charset, ctype, 2, match, 0)) {
272             r->content_type = apr_pstrcat(r->pool, ctype, ";charset=utf-8",
273                                           NULL);
274         } else {
275             char* str = apr_palloc(r->pool, strlen(r->content_type) + 13
276                                    - (match[0].rm_eo - match[0].rm_so) + 1);
277             memcpy(str, r->content_type, match[1].rm_so);
278             memcpy(str + match[1].rm_so, "utf-8", 5);
279             strcpy(str + match[1].rm_so + 5, r->content_type+match[1].rm_eo);
280             r->content_type = str;
281         }
282     }
283 }
284
285 static apr_status_t xml2enc_filter_init(ap_filter_t* f)
286 {
287     xml2ctx* ctx;
288     if (!f->ctx) {
289         xml2cfg* cfg = ap_get_module_config(f->r->per_dir_config,
290                                             &xml2enc_module);
291         f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(xml2ctx));
292         ctx->xml2enc = XML_CHAR_ENCODING_NONE;
293         if (cfg->skipto != NULL) {
294             ctx->flags |= ENC_SKIPTO;
295         }
296     }
297     return APR_SUCCESS;
298 }
299 static apr_status_t xml2enc_ffunc(ap_filter_t* f, apr_bucket_brigade* bb)
300 {
301     xml2ctx* ctx = f->ctx;
302     apr_status_t rv;
303     apr_bucket* b;
304     apr_bucket* bstart;
305     apr_size_t insz = 0;
306     char *ctype;
307     char *p;
308
309     if (!ctx || !f->r->content_type) {
310         /* log error about configuring this */
311         ap_remove_output_filter(f);
312         return ap_pass_brigade(f->next, bb) ;
313     }
314
315     ctype = apr_pstrdup(f->r->pool, f->r->content_type);
316     for (p = ctype; *p; ++p)
317         if (isupper(*p))
318             *p = tolower(*p);
319
320     /* only act if starts-with "text/" or contains "xml" */
321     if (strncmp(ctype, "text/", 5) && !strstr(ctype, "xml"))  {
322         ap_remove_output_filter(f);
323         return ap_pass_brigade(f->next, bb) ;
324     }
325
326     if (ctx->bbsave == NULL) {
327         ctx->bbsave = apr_brigade_create(f->r->pool,
328                                          f->r->connection->bucket_alloc);
329     }
330     /* append to any data left over from last time */
331     APR_BRIGADE_CONCAT(ctx->bbsave, bb);
332
333     if (!(ctx->flags & ENC_INITIALISED)) {
334         /* some kind of initialisation required */
335         /* Turn all this off when post-processing */
336
337         /* if we don't have enough data to sniff but more's to come, wait */
338         apr_brigade_length(ctx->bbsave, 0, &ctx->bblen);
339         if ((ctx->bblen < BUF_MIN) && (ctx->bblen != -1)) {
340             APR_BRIGADE_DO(b, ctx->bbsave) {
341                 if (APR_BUCKET_IS_EOS(b)) {
342                     ctx->flags |= ENC_SEEN_EOS;
343                     break;
344                 }
345             }
346             if (!(ctx->flags & ENC_SEEN_EOS)) {
347                 /* not enough data to sniff.  Wait for more */
348                 APR_BRIGADE_DO(b, ctx->bbsave) {
349                     rv = apr_bucket_setaside(b, f->r->pool);
350                     ap_assert(rv == APR_SUCCESS);
351                 }
352                 return APR_SUCCESS;
353             }
354         }
355         if (ctx->bblen == -1) {
356             ctx->bblen = BUFLEN-1;
357         }
358
359         /* flatten it into a NULL-terminated string */
360         ctx->buf = apr_palloc(f->r->pool, (apr_size_t)(ctx->bblen+1));
361         ctx->bytes = (apr_size_t)ctx->bblen;
362         rv = apr_brigade_flatten(ctx->bbsave, ctx->buf, &ctx->bytes);
363         ap_assert(rv == APR_SUCCESS);
364         ctx->buf[ctx->bytes] = 0;
365         sniff_encoding(f->r, ctx);
366
367         /* FIXME: hook here for rewriting start-of-data? */
368         /* nah, we only have one action here - call it inline */
369         fix_skipto(f->r, ctx);
370
371         /* we might change the Content-Length, so let's force its re-calculation */
372         apr_table_unset(f->r->headers_out, "Content-Length");
373
374         /* consume the data we just sniffed */
375         /* we need to omit any <meta> we just invalidated */
376         ctx->flags |= ENC_INITIALISED;
377         ap_set_module_config(f->r->request_config, &xml2enc_module, ctx);
378     }
379     if (ctx->bbnext == NULL) {
380         ctx->bbnext = apr_brigade_create(f->r->pool,
381                                          f->r->connection->bucket_alloc);
382     }
383
384     if (!ctx->convset) {
385         rv = ap_pass_brigade(f->next, ctx->bbsave);
386         apr_brigade_cleanup(ctx->bbsave);
387         ap_remove_output_filter(f);
388         return rv;
389     }
390     /* move the data back to bb */
391     APR_BRIGADE_CONCAT(bb, ctx->bbsave);
392
393     while (b = APR_BRIGADE_FIRST(bb), b != APR_BRIGADE_SENTINEL(bb)) {
394         ctx->bytes = 0;
395         if (APR_BUCKET_IS_METADATA(b)) {
396             APR_BUCKET_REMOVE(b);
397             if (APR_BUCKET_IS_EOS(b)) {
398                 /* send remaining data */
399                 APR_BRIGADE_INSERT_TAIL(ctx->bbnext, b);
400                 return ap_fflush(f->next, ctx->bbnext);
401             } else if (APR_BUCKET_IS_FLUSH(b)) {
402                 ap_fflush(f->next, ctx->bbnext);
403             }
404             apr_bucket_destroy(b);
405         }
406         else {        /* data bucket */
407             char* buf;
408             apr_size_t bytes = 0;
409             char fixbuf[BUFLEN];
410             apr_bucket* bdestroy = NULL;
411             if (insz > 0) { /* we have dangling data.  Flatten it. */
412                 buf = fixbuf;
413                 bytes = BUFLEN;
414                 rv = apr_brigade_flatten(bb, buf, &bytes);
415                 ap_assert(rv == APR_SUCCESS);
416                 if (bytes == insz) {
417                     /* this is only what we've already tried to convert.
418                      * The brigade is exhausted.
419                      * Save remaining data for next time round
420                      */
421           
422                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01437)
423                                   "xml2enc: Setting aside %" APR_SIZE_T_FMT
424                                   " unconverted bytes", bytes);
425                     rv = ap_fflush(f->next, ctx->bbnext);
426                     APR_BRIGADE_CONCAT(ctx->bbsave, bb);
427                     APR_BRIGADE_DO(b, ctx->bbsave) {
428                         ap_assert(apr_bucket_setaside(b, f->r->pool)
429                                   == APR_SUCCESS);
430                     }
431                     return rv;
432                 }
433                 /* remove the data we've just read */
434                 rv = apr_brigade_partition(bb, bytes, &bstart);
435                 while (b = APR_BRIGADE_FIRST(bb), b != bstart) {
436                     apr_bucket_delete(b);
437                 }
438                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01438)
439                               "xml2enc: consuming %" APR_SIZE_T_FMT
440                               " bytes flattened", bytes);
441             }
442             else {
443                 rv = apr_bucket_read(b, (const char**)&buf, &bytes,
444                                      APR_BLOCK_READ);
445                 APR_BUCKET_REMOVE(b);
446                 bdestroy = b;  /* can't destroy until finished with the data */
447                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01439)
448                               "xml2enc: consuming %" APR_SIZE_T_FMT
449                               " bytes from bucket", bytes);
450             }
451             /* OK, we've got some input we can use in [buf,bytes] */
452             if (rv == APR_SUCCESS) {
453                 apr_size_t consumed;
454                 xml2enc_run_preprocess(f, &buf, &bytes);
455                 consumed = insz = bytes;
456                 while (insz > 0) {
457                     apr_status_t rv2;
458                     if (ctx->bytes == ctx->bblen) {
459                         /* nothing was converted last time!
460                          * break out of this loop! 
461                          */
462                         b = apr_bucket_transient_create(buf+(bytes - insz), insz,
463                                                         bb->bucket_alloc);
464                         APR_BRIGADE_INSERT_HEAD(bb, b);
465                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01440)
466                                       "xml2enc: reinserting %" APR_SIZE_T_FMT
467                                       " unconsumed bytes from bucket", insz);
468                         break;
469                     }
470                     ctx->bytes = (apr_size_t)ctx->bblen;
471                     rv = apr_xlate_conv_buffer(ctx->convset, buf+(bytes - insz),
472                                                &insz, ctx->buf, &ctx->bytes);
473                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(01441)
474                                   "xml2enc: converted %" APR_SIZE_T_FMT
475                                   "/%" APR_OFF_T_FMT " bytes", consumed - insz,
476                                   ctx->bblen - ctx->bytes);
477                     consumed = insz;
478                     rv2 = ap_fwrite(f->next, ctx->bbnext, ctx->buf,
479                                     (apr_size_t)ctx->bblen - ctx->bytes);
480                     if (rv2 != APR_SUCCESS) {
481                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv2, f->r, APLOGNO(01442)
482                                       "ap_fwrite failed");
483                         return rv2;
484                     }
485                     switch (rv) {
486                     case APR_SUCCESS:
487                         continue;
488                     case APR_EINCOMPLETE:
489                         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, APLOGNO(01443)
490                                       "INCOMPLETE");
491                         continue;     /* If outbuf too small, go round again.
492                                        * If it was inbuf, we'll break out when
493                                        * we test ctx->bytes == ctx->bblen
494                                        */
495                     case APR_EINVAL: /* try skipping one bad byte */
496                         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01444)
497                                    "Skipping invalid byte(s) in input stream!");
498                         --insz;
499                         continue;
500                     default:
501                         /* Erk!  What's this?
502                          * Bail out, flush, and hope to eat the buf raw
503                          */
504                         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01445)
505                                       "Failed to convert input; trying it raw") ;
506                         ctx->convset = NULL;
507                         rv = ap_fflush(f->next, ctx->bbnext);
508                         if (rv != APR_SUCCESS)
509                             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, f->r, APLOGNO(01446)
510                                           "ap_fflush failed");
511                         else
512                             rv = ap_pass_brigade(f->next, ctx->bbnext);
513                     }
514                 }
515             } else {
516                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(01447)
517                               "xml2enc: error reading data") ;
518             }
519             if (bdestroy)
520                 apr_bucket_destroy(bdestroy);
521             if (rv != APR_SUCCESS)
522                 return rv;
523         }
524     }
525     return APR_SUCCESS;
526 }
527 static apr_status_t xml2enc_charset(request_rec* r, xmlCharEncoding* encp,
528                                     const char** encoding)
529 {
530     xml2ctx* ctx = ap_get_module_config(r->request_config, &xml2enc_module);
531     if (!ctx || !(ctx->flags & ENC_INITIALISED)) {
532         return APR_EAGAIN;
533     }
534     *encp = ctx->xml2enc;
535     *encoding = ctx->encoding;
536     return HAVE_ENCODING(ctx->xml2enc) ? APR_SUCCESS : APR_EGENERAL;
537 }
538
539 #define PROTO_FLAGS AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH
540 static void xml2enc_hooks(apr_pool_t* pool)
541 {
542     ap_register_output_filter_protocol("xml2enc", xml2enc_ffunc,
543                                        xml2enc_filter_init,
544                                        AP_FTYPE_RESOURCE, PROTO_FLAGS);
545     APR_REGISTER_OPTIONAL_FN(xml2enc_filter);
546     APR_REGISTER_OPTIONAL_FN(xml2enc_charset);
547     seek_meta_ctype = ap_pregcomp(pool,
548                        "(<meta[^>]*http-equiv[ \t\r\n='\"]*content-type[^>]*>)",
549                                   AP_REG_EXTENDED|AP_REG_ICASE) ;
550     seek_charset = ap_pregcomp(pool, "charset=([A-Za-z0-9_-]+)",
551                                AP_REG_EXTENDED|AP_REG_ICASE) ;
552 }
553 static const char* set_alias(cmd_parms* cmd, void* CFG,
554                              const char* charset, const char* alias)
555 {
556     const char* errmsg = ap_check_cmd_context(cmd, GLOBAL_ONLY);
557     if (errmsg != NULL)
558         return errmsg ;
559     else if (xmlAddEncodingAlias(charset, alias) == 0)
560         return NULL;
561     else
562         return "Error setting charset alias";
563 }
564
565 static const char* set_default(cmd_parms* cmd, void* CFG, const char* charset)
566 {
567     xml2cfg* cfg = CFG;
568     cfg->default_charset = charset;
569     cfg->default_encoding = xmlParseCharEncoding(charset);
570     switch(cfg->default_encoding) {
571     case XML_CHAR_ENCODING_NONE:
572         return "Default charset not found";
573     case XML_CHAR_ENCODING_ERROR:
574         return "Invalid or unsupported default charset";
575     default:
576         return NULL;
577     }
578 }
579 static const char* set_skipto(cmd_parms* cmd, void* CFG, const char* arg)
580 {
581     tattr* attr;
582     xml2cfg* cfg = CFG;
583     if (cfg->skipto == NULL)
584         cfg->skipto = apr_array_make(cmd->pool, 4, sizeof(tattr));
585     attr = apr_array_push(cfg->skipto) ;
586     attr->val = arg;
587     return NULL;
588 }
589
590 static const command_rec xml2enc_cmds[] = {
591     AP_INIT_TAKE1("xml2EncDefault", set_default, NULL, OR_ALL,
592                   "Usage: xml2EncDefault charset"),
593     AP_INIT_ITERATE2("xml2EncAlias", set_alias, NULL, RSRC_CONF,
594                      "EncodingAlias charset alias [more aliases]"),
595     AP_INIT_ITERATE("xml2StartParse", set_skipto, NULL, OR_ALL,
596                     "Ignore anything in front of the first of these elements"),
597     { NULL }
598 };
599 static void* xml2enc_config(apr_pool_t* pool, char* x)
600 {
601     xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
602     ret->default_encoding = XML_CHAR_ENCODING_NONE ;
603     return ret;
604 }
605
606 static void* xml2enc_merge(apr_pool_t* pool, void* BASE, void* ADD)
607 {
608     xml2cfg* base = BASE;
609     xml2cfg* add = ADD;
610     xml2cfg* ret = apr_pcalloc(pool, sizeof(xml2cfg));
611     ret->default_encoding = (add->default_encoding == XML_CHAR_ENCODING_NONE)
612                           ? base->default_encoding : add->default_encoding ;
613     ret->default_charset = add->default_charset
614                          ? add->default_charset : base->default_charset;
615     ret->skipto = add->skipto ? add->skipto : base->skipto;
616     return ret;
617 }
618
619 AP_DECLARE_MODULE(xml2enc) = {
620     STANDARD20_MODULE_STUFF,
621     xml2enc_config,
622     xml2enc_merge,
623     NULL,
624     NULL,
625     xml2enc_cmds,
626     xml2enc_hooks
627 };
628
629 APR_IMPLEMENT_OPTIONAL_HOOK_RUN_ALL(xml2enc, XML2ENC, int, preprocess,
630                       (ap_filter_t *f, char** bufp, apr_size_t* bytesp),
631                       (f, bufp, bytesp), OK, DECLINED)