]> granicus.if.org Git - apache/blob - modules/filters/mod_proxy_html.c
mod_cache: Don't add cached/revalidated entity headers to a 304 response.
[apache] / modules / filters / mod_proxy_html.c
1 /*      Copyright (c) 2003-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 /*      GO_FASTER
21         You can #define GO_FASTER to disable trace logging.
22 */
23
24 #ifdef GO_FASTER
25 #define VERBOSE(x)
26 #define VERBOSEB(x)
27 #else
28 #define VERBOSE(x) if (verbose) x
29 #define VERBOSEB(x) if (verbose) {x}
30 #endif
31
32 /* libxml2 */
33 #include <libxml/HTMLparser.h>
34
35 #include "http_protocol.h"
36 #include "http_config.h"
37 #include "http_log.h"
38 #include "apr_strings.h"
39 #include "apr_hash.h"
40 #include "apr_strmatch.h"
41 #include "apr_lib.h"
42
43 #include "apr_optional.h"
44 #include "mod_xml2enc.h"
45 #include "http_request.h"
46 #include "ap_expr.h"
47
48 /* globals set once at startup */
49 static ap_rxplus_t *old_expr;
50 static ap_regex_t *seek_meta;
51 static const apr_strmatch_pattern* seek_content;
52 static apr_status_t (*xml2enc_charset)(request_rec*, xmlCharEncoding*, const char**) = NULL;
53 static apr_status_t (*xml2enc_filter)(request_rec*, const char*, unsigned int) = NULL;
54
55 module AP_MODULE_DECLARE_DATA proxy_html_module;
56
57 #define M_HTML                  0x01
58 #define M_EVENTS                0x02
59 #define M_CDATA                 0x04
60 #define M_REGEX                 0x08
61 #define M_ATSTART               0x10
62 #define M_ATEND                 0x20
63 #define M_LAST                  0x40
64 #define M_NOTLAST               0x80
65 #define M_INTERPOLATE_TO        0x100
66 #define M_INTERPOLATE_FROM      0x200
67
68 typedef struct {
69     const char *val;
70 } tattr;
71 typedef struct {
72     unsigned int start;
73     unsigned int end;
74 } meta;
75 typedef struct urlmap {
76     struct urlmap *next;
77     unsigned int flags;
78     unsigned int regflags;
79     union {
80         const char *c;
81         ap_regex_t *r;
82     } from;
83     const char *to;
84     ap_expr_info_t *cond;
85 } urlmap;
86 typedef struct {
87     urlmap *map;
88     const char *doctype;
89     const char *etag;
90     unsigned int flags;
91     size_t bufsz;
92     apr_hash_t *links;
93     apr_array_header_t *events;
94     const char *charset_out;
95     int extfix;
96     int metafix;
97     int strip_comments;
98     int interp;
99     int enabled;
100 } proxy_html_conf;
101 typedef struct {
102     ap_filter_t *f;
103     proxy_html_conf *cfg;
104     htmlParserCtxtPtr parser;
105     apr_bucket_brigade *bb;
106     char *buf;
107     size_t offset;
108     size_t avail;
109     const char *encoding;
110     urlmap *map;
111 } saxctxt;
112
113
114 #define NORM_LC 0x1
115 #define NORM_MSSLASH 0x2
116 #define NORM_RESET 0x4
117 static htmlSAXHandler sax;
118
119 typedef enum { ATTR_IGNORE, ATTR_URI, ATTR_EVENT } rewrite_t;
120
121 static const char *const fpi_html =
122         "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n";
123 static const char *const fpi_html_legacy =
124         "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
125 static const char *const fpi_xhtml =
126         "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
127 static const char *const fpi_xhtml_legacy =
128         "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
129 static const char *const fpi_html5 = "<!DOCTYPE html>\n";
130 static const char *const html_etag = ">";
131 static const char *const xhtml_etag = " />";
132 /*#define DEFAULT_DOCTYPE fpi_html */
133 static const char *const DEFAULT_DOCTYPE = "";
134 #define DEFAULT_ETAG html_etag
135
136 static void normalise(unsigned int flags, char *str)
137 {
138     char *p;
139     if (flags & NORM_LC)
140         for (p = str; *p; ++p)
141             if (isupper(*p))
142                 *p = tolower(*p);
143
144     if (flags & NORM_MSSLASH)
145         for (p = ap_strchr(str, '\\'); p; p = ap_strchr(p+1, '\\'))
146             *p = '/';
147
148 }
149 #define consume_buffer(ctx,inbuf,bytes,flag) \
150         htmlParseChunk(ctx->parser, inbuf, bytes, flag)
151
152 #define AP_fwrite(ctx,inbuf,bytes,flush) \
153         ap_fwrite(ctx->f->next, ctx->bb, inbuf, bytes);
154
155 /* This is always utf-8 on entry.  We can convert charset within FLUSH */
156 #define FLUSH AP_fwrite(ctx, (chars+begin), (i-begin), 0); begin = i+1
157 static void pcharacters(void *ctxt, const xmlChar *uchars, int length)
158 {
159     const char *chars = (const char*) uchars;
160     saxctxt *ctx = (saxctxt*) ctxt;
161     int i;
162     int begin;
163     for (begin=i=0; i<length; i++) {
164         switch (chars[i]) {
165         case '&' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&amp;"); break;
166         case '<' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&lt;"); break;
167         case '>' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&gt;"); break;
168         case '"' : FLUSH; ap_fputs(ctx->f->next, ctx->bb, "&quot;"); break;
169         default : break;
170         }
171     }
172     FLUSH;
173 }
174
175 static void preserve(saxctxt *ctx, const size_t len)
176 {
177     char *newbuf;
178     if (len <= (ctx->avail - ctx->offset))
179         return;
180     else while (len > (ctx->avail - ctx->offset))
181         ctx->avail += ctx->cfg->bufsz;
182
183     newbuf = realloc(ctx->buf, ctx->avail);
184     if (newbuf != ctx->buf) {
185         if (ctx->buf)
186             apr_pool_cleanup_kill(ctx->f->r->pool, ctx->buf,
187                                   (int(*)(void*))free);
188         apr_pool_cleanup_register(ctx->f->r->pool, newbuf,
189                                   (int(*)(void*))free, apr_pool_cleanup_null);
190         ctx->buf = newbuf;
191     }
192 }
193
194 static void pappend(saxctxt *ctx, const char *buf, const size_t len)
195 {
196     preserve(ctx, len);
197     memcpy(ctx->buf+ctx->offset, buf, len);
198     ctx->offset += len;
199 }
200
201 static void dump_content(saxctxt *ctx)
202 {
203     urlmap *m;
204     char *found;
205     size_t s_from, s_to;
206     size_t match;
207     char c = 0;
208     int nmatch;
209     ap_regmatch_t pmatch[10];
210     char *subs;
211     size_t len, offs;
212     urlmap *themap = ctx->map;
213 #ifndef GO_FASTER
214     int verbose = APLOGrtrace1(ctx->f->r);
215 #endif
216
217     pappend(ctx, &c, 1);        /* append null byte */
218         /* parse the text for URLs */
219     for (m = themap; m; m = m->next) {
220         if (!(m->flags & M_CDATA))
221             continue;
222         if (m->flags & M_REGEX) {
223             nmatch = 10;
224             offs = 0;
225             while (!ap_regexec(m->from.r, ctx->buf+offs, nmatch, pmatch, 0)) {
226                 match = pmatch[0].rm_so;
227                 s_from = pmatch[0].rm_eo - match;
228                 subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
229                                   nmatch, pmatch);
230                 s_to = strlen(subs);
231                 len = strlen(ctx->buf);
232                 offs += match;
233                 VERBOSEB(
234                     const char *f = apr_pstrndup(ctx->f->r->pool,
235                     ctx->buf + offs, s_from);
236                     ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, ctx->f->r,
237                                   "C/RX: match at %s, substituting %s", f, subs);
238                 )
239                 if (s_to > s_from) {
240                     preserve(ctx, s_to - s_from);
241                     memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
242                             len + 1 - s_from - offs);
243                     memcpy(ctx->buf+offs, subs, s_to);
244                 }
245                 else {
246                     memcpy(ctx->buf + offs, subs, s_to);
247                     memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
248                             len + 1 - s_from - offs);
249                 }
250                 offs += s_to;
251             }
252         }
253         else {
254             s_from = strlen(m->from.c);
255             s_to = strlen(m->to);
256             for (found = strstr(ctx->buf, m->from.c); found;
257                  found = strstr(ctx->buf+match+s_to, m->from.c)) {
258                 match = found - ctx->buf;
259                 if ((m->flags & M_ATSTART) && (match != 0))
260                     break;
261                 len = strlen(ctx->buf);
262                 if ((m->flags & M_ATEND) && (match < (len - s_from)))
263                     continue;
264                 VERBOSE(ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, ctx->f->r,
265                                       "C: matched %s, substituting %s",
266                                       m->from.c, m->to));
267                 if (s_to > s_from) {
268                     preserve(ctx, s_to - s_from);
269                     memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
270                             len + 1 - s_from - match);
271                     memcpy(ctx->buf+match, m->to, s_to);
272                 }
273                 else {
274                     memcpy(ctx->buf+match, m->to, s_to);
275                     memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
276                             len + 1 - s_from - match);
277                 }
278             }
279         }
280     }
281     AP_fwrite(ctx, ctx->buf, strlen(ctx->buf), 1);
282 }
283 static void pcdata(void *ctxt, const xmlChar *uchars, int length)
284 {
285     const char *chars = (const char*) uchars;
286     saxctxt *ctx = (saxctxt*) ctxt;
287     if (ctx->cfg->extfix) {
288         pappend(ctx, chars, length);
289     }
290     else {
291         /* not sure if this should force-flush
292          * (i.e. can one cdata section come in multiple calls?)
293          */
294         AP_fwrite(ctx, chars, length, 0);
295     }
296 }
297 static void pcomment(void *ctxt, const xmlChar *uchars)
298 {
299     const char *chars = (const char*) uchars;
300     saxctxt *ctx = (saxctxt*) ctxt;
301     if (ctx->cfg->strip_comments)
302         return;
303
304     if (ctx->cfg->extfix) {
305         pappend(ctx, "<!--", 4);
306         pappend(ctx, chars, strlen(chars));
307         pappend(ctx, "-->", 3);
308     }
309     else {
310         ap_fputs(ctx->f->next, ctx->bb, "<!--");
311         AP_fwrite(ctx, chars, strlen(chars), 1);
312         ap_fputs(ctx->f->next, ctx->bb, "-->");
313         dump_content(ctx);
314     }
315 }
316 static void pendElement(void *ctxt, const xmlChar *uname)
317 {
318     saxctxt *ctx = (saxctxt*) ctxt;
319     const char *name = (const char*) uname;
320     const htmlElemDesc* desc = htmlTagLookup(uname);
321
322     if ((ctx->cfg->doctype == fpi_html) || (ctx->cfg->doctype == fpi_xhtml)) {
323         /* enforce html */
324         if (!desc || desc->depr)
325             return;
326     
327     }
328     else if ((ctx->cfg->doctype == fpi_html_legacy)
329              || (ctx->cfg->doctype == fpi_xhtml_legacy)) {
330         /* enforce html legacy */
331         if (!desc)
332             return;
333     }
334     /* TODO - implement HTML "allowed here" using the stack */
335     /* nah.  Keeping the stack is too much overhead */
336
337     if (ctx->offset > 0) {
338         dump_content(ctx);
339         ctx->offset = 0;        /* having dumped it, we can re-use the memory */
340     }
341     if (!desc || !desc->empty) {
342         ap_fprintf(ctx->f->next, ctx->bb, "</%s>", name);
343     }
344 }
345
346 static void pstartElement(void *ctxt, const xmlChar *uname,
347                           const xmlChar** uattrs)
348 {
349     int required_attrs;
350     int num_match;
351     size_t offs, len;
352     char *subs;
353     rewrite_t is_uri;
354     const char** a;
355     urlmap *m;
356     size_t s_to, s_from, match;
357     char *found;
358     saxctxt *ctx = (saxctxt*) ctxt;
359     size_t nmatch;
360     ap_regmatch_t pmatch[10];
361 #ifndef GO_FASTER
362     int verbose = APLOGrtrace1(ctx->f->r);
363 #endif
364     apr_array_header_t *linkattrs;
365     int i;
366     const char *name = (const char*) uname;
367     const char** attrs = (const char**) uattrs;
368     const htmlElemDesc* desc = htmlTagLookup(uname);
369     urlmap *themap = ctx->map;
370 #ifdef HAVE_STACK
371     const void** descp;
372 #endif
373     int enforce = 0;
374     if ((ctx->cfg->doctype == fpi_html) || (ctx->cfg->doctype == fpi_xhtml)) {
375         /* enforce html */
376         enforce = 2;
377         if (!desc || desc->depr)
378             return;
379     
380     }
381     else if ((ctx->cfg->doctype == fpi_html)
382              || (ctx->cfg->doctype == fpi_xhtml)) {
383         enforce = 1;
384         /* enforce html legacy */
385         if (!desc) {
386             return;
387         }
388     }
389     if (!desc && enforce) {
390         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01416)
391                       "Bogus HTML element %s dropped", name);
392         return;
393     }
394     if (desc && desc->depr && (enforce == 2)) {
395         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01417)
396                       "Deprecated HTML element %s dropped", name);
397         return;
398     }
399 #ifdef HAVE_STACK
400     descp = apr_array_push(ctx->stack);
401     *descp = desc;
402     /* TODO - implement HTML "allowed here" */
403 #endif
404
405     ap_fputc(ctx->f->next, ctx->bb, '<');
406     ap_fputs(ctx->f->next, ctx->bb, name);
407
408     required_attrs = 0;
409     if ((enforce > 0) && (desc != NULL) && (desc->attrs_req != NULL))
410         for (a = desc->attrs_req; *a; a++)
411             ++required_attrs;
412
413     if (attrs) {
414         linkattrs = apr_hash_get(ctx->cfg->links, name, APR_HASH_KEY_STRING);
415         for (a = attrs; *a; a += 2) {
416             if (desc && enforce > 0) {
417                 switch (htmlAttrAllowed(desc, (xmlChar*)*a, 2-enforce)) {
418                 case HTML_INVALID:
419                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01418)
420                                   "Bogus HTML attribute %s of %s dropped",
421                                   *a, name);
422                     continue;
423                 case HTML_DEPRECATED:
424                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01419)
425                                   "Deprecated HTML attribute %s of %s dropped",
426                                   *a, name);
427                     continue;
428                 case HTML_REQUIRED:
429                     required_attrs--;   /* cross off the number still needed */
430                 /* fallthrough - required implies valid */
431                 default:
432                     break;
433                 }
434             }
435             ctx->offset = 0;
436             if (a[1]) {
437                 pappend(ctx, a[1], strlen(a[1])+1);
438                 is_uri = ATTR_IGNORE;
439                 if (linkattrs) {
440                     tattr *attrs = (tattr*) linkattrs->elts;
441                     for (i=0; i < linkattrs->nelts; ++i) {
442                         if (!strcmp(*a, attrs[i].val)) {
443                             is_uri = ATTR_URI;
444                             break;
445                         }
446                     }
447                 }
448                 if ((is_uri == ATTR_IGNORE) && ctx->cfg->extfix
449                     && (ctx->cfg->events != NULL)) {
450                     for (i=0; i < ctx->cfg->events->nelts; ++i) {
451                         tattr *attrs = (tattr*) ctx->cfg->events->elts;
452                         if (!strcmp(*a, attrs[i].val)) {
453                             is_uri = ATTR_EVENT;
454                             break;
455                         }
456                     }
457                 }
458                 switch (is_uri) {
459                 case ATTR_URI:
460                     num_match = 0;
461                     for (m = themap; m; m = m->next) {
462                         if (!(m->flags & M_HTML))
463                             continue;
464                         if (m->flags & M_REGEX) {
465                             nmatch = 10;
466                             if (!ap_regexec(m->from.r, ctx->buf, nmatch,
467                                             pmatch, 0)) {
468                                 ++num_match;
469                                 offs = match = pmatch[0].rm_so;
470                                 s_from = pmatch[0].rm_eo - match;
471                                 subs = ap_pregsub(ctx->f->r->pool, m->to,
472                                                   ctx->buf, nmatch, pmatch);
473                                 VERBOSE({
474                                     const char *f;
475                                     f = apr_pstrndup(ctx->f->r->pool,
476                                                      ctx->buf + offs, s_from);
477                                     ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0,
478                                                   ctx->f->r,
479                                          "H/RX: match at %s, substituting %s",
480                                                   f, subs);
481                                 })
482                                 s_to = strlen(subs);
483                                 len = strlen(ctx->buf);
484                                 if (s_to > s_from) {
485                                     preserve(ctx, s_to - s_from);
486                                     memmove(ctx->buf+offs+s_to,
487                                             ctx->buf+offs+s_from,
488                                             len + 1 - s_from - offs);
489                                     memcpy(ctx->buf+offs, subs, s_to);
490                                 }
491                                 else {
492                                     memcpy(ctx->buf + offs, subs, s_to);
493                                     memmove(ctx->buf+offs+s_to,
494                                             ctx->buf+offs+s_from,
495                                             len + 1 - s_from - offs);
496                                 }
497                             }
498                         } else {
499                             s_from = strlen(m->from.c);
500                             if (!strncasecmp(ctx->buf, m->from.c, s_from)) {
501                                 ++num_match;
502                                 s_to = strlen(m->to);
503                                 len = strlen(ctx->buf);
504                                 VERBOSE(ap_log_rerror(APLOG_MARK, APLOG_TRACE3,
505                                                       0, ctx->f->r,
506                                               "H: matched %s, substituting %s",
507                                                       m->from.c, m->to));
508                                 if (s_to > s_from) {
509                                     preserve(ctx, s_to - s_from);
510                                     memmove(ctx->buf+s_to, ctx->buf+s_from,
511                                             len + 1 - s_from);
512                                     memcpy(ctx->buf, m->to, s_to);
513                                 }
514                                 else {     /* it fits in the existing space */
515                                     memcpy(ctx->buf, m->to, s_to);
516                                     memmove(ctx->buf+s_to, ctx->buf+s_from,
517                                             len + 1 - s_from);
518                                 }
519                                 break;
520                             }
521                         }
522                         /* URIs only want one match unless overridden in the config */
523                         if ((num_match > 0) && !(m->flags & M_NOTLAST))
524                             break;
525                     }
526                     break;
527                 case ATTR_EVENT:
528                     for (m = themap; m; m = m->next) {
529                         num_match = 0;        /* reset here since we're working per-rule */
530                         if (!(m->flags & M_EVENTS))
531                             continue;
532                         if (m->flags & M_REGEX) {
533                             nmatch = 10;
534                             offs = 0;
535                             while (!ap_regexec(m->from.r, ctx->buf+offs,
536                                                nmatch, pmatch, 0)) {
537                                 match = pmatch[0].rm_so;
538                                 s_from = pmatch[0].rm_eo - match;
539                                 subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
540                                                     nmatch, pmatch);
541                                 VERBOSE({
542                                     const char *f;
543                                     f = apr_pstrndup(ctx->f->r->pool,
544                                                      ctx->buf + offs, s_from);
545                                     ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0,
546                                                   ctx->f->r,
547                                            "E/RX: match at %s, substituting %s",
548                                                   f, subs);
549                                 })
550                                 s_to = strlen(subs);
551                                 offs += match;
552                                 len = strlen(ctx->buf);
553                                 if (s_to > s_from) {
554                                     preserve(ctx, s_to - s_from);
555                                     memmove(ctx->buf+offs+s_to,
556                                             ctx->buf+offs+s_from,
557                                             len + 1 - s_from - offs);
558                                     memcpy(ctx->buf+offs, subs, s_to);
559                                 }
560                                 else {
561                                     memcpy(ctx->buf + offs, subs, s_to);
562                                     memmove(ctx->buf+offs+s_to,
563                                             ctx->buf+offs+s_from,
564                                             len + 1 - s_from - offs);
565                                 }
566                                 offs += s_to;
567                                 ++num_match;
568                             }
569                         }
570                         else {
571                             found = strstr(ctx->buf, m->from.c);
572                             if ((m->flags & M_ATSTART) && (found != ctx->buf))
573                                 continue;
574                             while (found) {
575                                 s_from = strlen(m->from.c);
576                                 s_to = strlen(m->to);
577                                 match = found - ctx->buf;
578                                 if ((s_from < strlen(found))
579                                     && (m->flags & M_ATEND)) {
580                                     found = strstr(ctx->buf+match+s_from,
581                                                    m->from.c);
582                                     continue;
583                                 }
584                                 else {
585                                     found = strstr(ctx->buf+match+s_to,
586                                                    m->from.c);
587                                 }
588                                 VERBOSE(ap_log_rerror(APLOG_MARK, APLOG_TRACE3,
589                                                       0, ctx->f->r,
590                                               "E: matched %s, substituting %s",
591                                                       m->from.c, m->to));
592                                 len = strlen(ctx->buf);
593                                 if (s_to > s_from) {
594                                     preserve(ctx, s_to - s_from);
595                                     memmove(ctx->buf+match+s_to,
596                                             ctx->buf+match+s_from,
597                                             len + 1 - s_from - match);
598                                     memcpy(ctx->buf+match, m->to, s_to);
599                                 }
600                                 else {
601                                     memcpy(ctx->buf+match, m->to, s_to);
602                                     memmove(ctx->buf+match+s_to,
603                                             ctx->buf+match+s_from,
604                                             len + 1 - s_from - match);
605                                 }
606                                 ++num_match;
607                             }
608                         }
609                         if (num_match && (m->flags & M_LAST))
610                             break;
611                     }
612                     break;
613                 case ATTR_IGNORE:
614                     break;
615                 }
616             }
617             if (!a[1])
618                 ap_fputstrs(ctx->f->next, ctx->bb, " ", a[0], NULL);
619             else {
620
621                 if (ctx->cfg->flags != 0)
622                     normalise(ctx->cfg->flags, ctx->buf);
623
624                 /* write the attribute, using pcharacters to html-escape
625                    anything that needs it in the value.
626                 */
627                 ap_fputstrs(ctx->f->next, ctx->bb, " ", a[0], "=\"", NULL);
628                 pcharacters(ctx, (const xmlChar*)ctx->buf, strlen(ctx->buf));
629                 ap_fputc(ctx->f->next, ctx->bb, '"');
630             }
631         }
632     }
633     ctx->offset = 0;
634     if (desc && desc->empty)
635         ap_fputs(ctx->f->next, ctx->bb, ctx->cfg->etag);
636     else
637         ap_fputc(ctx->f->next, ctx->bb, '>');
638
639     if ((enforce > 0) && (required_attrs > 0)) {
640         /* if there are more required attributes than we found then complain */
641         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->f->r, APLOGNO(01420)
642                       "HTML element %s is missing %d required attributes",
643                       name, required_attrs);
644     }
645 }
646
647 static meta *metafix(request_rec *r, const char *buf)
648 {
649     meta *ret = NULL;
650     size_t offs = 0;
651     const char *p;
652     const char *q;
653     char *header;
654     char *content;
655     ap_regmatch_t pmatch[2];
656     char delim;
657
658     while (!ap_regexec(seek_meta, buf+offs, 2, pmatch, 0)) {
659         header = NULL;
660         content = NULL;
661         p = buf+offs+pmatch[1].rm_eo;
662         while (!apr_isalpha(*++p));
663         for (q = p; apr_isalnum(*q) || (*q == '-'); ++q);
664         header = apr_pstrndup(r->pool, p, q-p);
665         if (strncasecmp(header, "Content-", 8)) {
666             /* find content=... string */
667             p = apr_strmatch(seek_content, buf+offs+pmatch[0].rm_so,
668                               pmatch[0].rm_eo - pmatch[0].rm_so);
669             /* if it doesn't contain "content", ignore, don't crash! */
670             if (p != NULL) {
671                 while (*p) {
672                     p += 7;
673                     while (apr_isspace(*p))
674                         ++p;
675                     /* XXX Should we search for another content= pattern? */
676                     if (*p != '=')
677                         break;
678                     while (*p && apr_isspace(*++p));
679                     if ((*p == '\'') || (*p == '"')) {
680                         delim = *p++;
681                         for (q = p; *q && *q != delim; ++q);
682                         /* No terminating delimiter found? Skip the boggus directive */
683                         if (*q != delim)
684                            break;
685                     } else {
686                         for (q = p; *q && !apr_isspace(*q) && (*q != '>'); ++q);
687                     }
688                     content = apr_pstrndup(r->pool, p, q-p);
689                     break;
690                 }
691             }
692         }
693         else if (!strncasecmp(header, "Content-Type", 12)) {
694             ret = apr_palloc(r->pool, sizeof(meta));
695             ret->start = offs+pmatch[0].rm_so;
696             ret->end = offs+pmatch[0].rm_eo;
697         }
698         if (header && content) {
699 #ifndef GO_FASTER
700             ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
701                           "Adding header [%s: %s] from HTML META",
702                           header, content); 
703 #endif
704             apr_table_setn(r->headers_out, header, content);
705         }
706         offs += pmatch[0].rm_eo;
707     }
708     return ret;
709 }
710
711 static const char *interpolate_vars(request_rec *r, const char *str)
712 {
713     const char *start;
714     const char *end;
715     const char *delim;
716     const char *before;
717     const char *after;
718     const char *replacement;
719     const char *var;
720     for (;;) {
721         start = str;
722         if (start = ap_strstr_c(start, "${"), start == NULL)
723             break;
724
725         if (end = ap_strchr_c(start+2, '}'), end == NULL)
726             break;
727
728         delim = ap_strchr_c(start, '|');
729         before = apr_pstrndup(r->pool, str, start-str);
730         after = end+1;
731         if (delim) {
732             var = apr_pstrndup(r->pool, start+2, delim-start-2);
733         }
734         else {
735             var = apr_pstrndup(r->pool, start+2, end-start-2);
736         }
737         replacement = apr_table_get(r->subprocess_env, var);
738         if (!replacement) {
739             if (delim)
740                 replacement = apr_pstrndup(r->pool, delim+1, end-delim-1);
741             else
742                 replacement = "";
743         }
744         str = apr_pstrcat(r->pool, before, replacement, after, NULL);
745         ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
746                       "Interpolating %s  =>  %s", var, replacement);
747     }
748     return str;
749 }
750 static void fixup_rules(saxctxt *ctx)
751 {
752     urlmap *newp;
753     urlmap *p;
754     urlmap *prev = NULL;
755     request_rec *r = ctx->f->r;
756
757     for (p = ctx->cfg->map; p; p = p->next) {
758         if (p->cond != NULL) {
759             const char *err;
760             int ok = ap_expr_exec(r, p->cond, &err);
761             if (err) {
762                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01421)
763                               "Error evaluating expr: %s", err);
764             }
765             if (ok == 0) {
766                 continue;  /* condition is unsatisfied */
767             }
768         }
769
770         newp = apr_pmemdup(r->pool, p, sizeof(urlmap));
771
772         if (newp->flags & M_INTERPOLATE_FROM) {
773             newp->from.c = interpolate_vars(r, newp->from.c);
774             if (!newp->from.c || !*newp->from.c)
775                 continue;        /* don't use empty from-pattern */
776             if (newp->flags & M_REGEX) {
777                 newp->from.r = ap_pregcomp(r->pool, newp->from.c,
778                                            newp->regflags);
779             }
780         }
781         if (newp->flags & M_INTERPOLATE_TO) {
782             newp->to = interpolate_vars(r, newp->to);
783         }
784         /* evaluate p->cond; continue if unsatisfied */
785         /* create new urlmap with memcpy and append to map */
786         /* interpolate from if flagged to do so */
787         /* interpolate to if flagged to do so */
788
789         if (prev != NULL)
790             prev->next = newp;
791         else
792             ctx->map = newp;
793         prev = newp;
794     }
795
796     if (prev)
797         prev->next = NULL;
798 }
799
800 static saxctxt *check_filter_init (ap_filter_t *f)
801 {
802     saxctxt *fctx;
803     if (!f->ctx) {
804         proxy_html_conf *cfg;
805         const char *force;
806         const char *errmsg = NULL;
807         cfg = ap_get_module_config(f->r->per_dir_config, &proxy_html_module);
808         force = apr_table_get(f->r->subprocess_env, "PROXY_HTML_FORCE");
809
810         if (!force) {
811             if (!f->r->proxyreq) {
812                 errmsg = "Non-proxy request; not inserting proxy-html filter";
813             }
814             else if (!f->r->content_type) {
815                 errmsg = "No content-type; bailing out of proxy-html filter";
816             }
817             else if (strncasecmp(f->r->content_type, "text/html", 9) &&
818                      strncasecmp(f->r->content_type,
819                                  "application/xhtml+xml", 21)) {
820                 errmsg = "Non-HTML content; not inserting proxy-html filter";
821             }
822         }
823         if (!cfg->links) {
824             errmsg = "No links configured: nothing for proxy-html filter to do";
825         }
826
827         if (errmsg) {
828 #ifndef GO_FASTER
829             ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, f->r, "%s", errmsg);
830 #endif
831             ap_remove_output_filter(f);
832             return NULL;
833         }
834
835         fctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(saxctxt));
836         fctx->f = f;
837         fctx->bb = apr_brigade_create(f->r->pool,
838                                       f->r->connection->bucket_alloc);
839         fctx->cfg = cfg;
840         apr_table_unset(f->r->headers_out, "Content-Length");
841
842         if (cfg->interp)
843             fixup_rules(fctx);
844         else
845             fctx->map = cfg->map;
846         /* defer dealing with charset_out until after sniffing charset_in
847          * so we can support setting one to t'other.
848          */
849     }
850     return f->ctx;
851 }
852
853 static apr_status_t proxy_html_filter(ap_filter_t *f, apr_bucket_brigade *bb)
854 {
855     apr_bucket* b;
856     meta *m = NULL;
857     xmlCharEncoding enc;
858     const char *buf = 0;
859     apr_size_t bytes = 0;
860 #ifndef USE_OLD_LIBXML2
861     int xmlopts = XML_PARSE_RECOVER | XML_PARSE_NONET |
862                   XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING;
863 #endif
864
865     saxctxt *ctxt = check_filter_init(f);
866     if (!ctxt)
867         return ap_pass_brigade(f->next, bb);
868     for (b = APR_BRIGADE_FIRST(bb);
869          b != APR_BRIGADE_SENTINEL(bb);
870          b = APR_BUCKET_NEXT(b)) {
871         if (APR_BUCKET_IS_METADATA(b)) {
872             if (APR_BUCKET_IS_EOS(b)) {
873                 if (ctxt->parser != NULL) {
874                     consume_buffer(ctxt, buf, 0, 1);
875                 }
876                 APR_BRIGADE_INSERT_TAIL(ctxt->bb,
877                 apr_bucket_eos_create(ctxt->bb->bucket_alloc));
878                 ap_pass_brigade(ctxt->f->next, ctxt->bb);
879             }
880             else if (APR_BUCKET_IS_FLUSH(b)) {
881                 /* pass on flush, except at start where it would cause
882                  * headers to be sent before doc sniffing
883                  */
884                 if (ctxt->parser != NULL) {
885                     ap_fflush(ctxt->f->next, ctxt->bb);
886                 }
887             }
888         }
889         else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
890                  == APR_SUCCESS) {
891             if (ctxt->parser == NULL) {
892                 const char *cenc;
893                 if (!xml2enc_charset ||
894                     (xml2enc_charset(f->r, &enc, &cenc) != APR_SUCCESS)) {
895                     if (!xml2enc_charset)
896                         ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01422)
897                      "No i18n support found.  Install mod_xml2enc if required");
898                     enc = XML_CHAR_ENCODING_NONE;
899                     ap_set_content_type(f->r, "text/html;charset=utf-8");
900                 }
901                 else {
902                     /* if we wanted a non-default charset_out, insert the
903                      * xml2enc filter now that we've sniffed it
904                      */
905                     if (ctxt->cfg->charset_out && xml2enc_filter) {
906                         if (*ctxt->cfg->charset_out != '*')
907                             cenc = ctxt->cfg->charset_out;
908                         xml2enc_filter(f->r, cenc, ENCIO_OUTPUT);
909                         ap_set_content_type(f->r,
910                                             apr_pstrcat(f->r->pool,
911                                                         "text/html;charset=",
912                                                         cenc, NULL));
913                     }
914                     else /* Normal case, everything worked, utf-8 output */
915                         ap_set_content_type(f->r, "text/html;charset=utf-8");
916                 }
917
918                 ap_fputs(f->next, ctxt->bb, ctxt->cfg->doctype);
919                 ctxt->parser = htmlCreatePushParserCtxt(&sax, ctxt, buf,
920                                                         4, 0, enc);
921                 buf += 4;
922                 bytes -= 4;
923                 if (ctxt->parser == NULL) {
924                     apr_status_t rv = ap_pass_brigade(f->next, bb);
925                     ap_remove_output_filter(f);
926                     return rv;
927                 }
928                 apr_pool_cleanup_register(f->r->pool, ctxt->parser,
929                                           (int(*)(void*))htmlFreeParserCtxt,
930                                           apr_pool_cleanup_null);
931 #ifndef USE_OLD_LIBXML2
932                 if (xmlopts = xmlCtxtUseOptions(ctxt->parser, xmlopts), xmlopts)
933                     ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r, APLOGNO(01423)
934                                   "Unsupported parser opts %x", xmlopts);
935 #endif
936                 if (ctxt->cfg->metafix)
937                     m = metafix(f->r, buf);
938                 if (m) {
939                     consume_buffer(ctxt, buf, m->start, 0);
940                     consume_buffer(ctxt, buf+m->end, bytes-m->end, 0);
941                 }
942                 else {
943                     consume_buffer(ctxt, buf, bytes, 0);
944                 }
945             }
946             else {
947                 consume_buffer(ctxt, buf, bytes, 0);
948             }
949         }
950         else {
951             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(01424)
952                           "Error in bucket read");
953         }
954     }
955     /*ap_fflush(ctxt->f->next, ctxt->bb);        // uncomment for debug */
956     apr_brigade_cleanup(bb);
957     return APR_SUCCESS;
958 }
959
960 static void *proxy_html_config(apr_pool_t *pool, char *x)
961 {
962     proxy_html_conf *ret = apr_pcalloc(pool, sizeof(proxy_html_conf));
963     ret->doctype = DEFAULT_DOCTYPE;
964     ret->etag = DEFAULT_ETAG;
965     ret->bufsz = 8192;
966     /* ret->interp = 1; */
967     /* don't initialise links and events until they get set/used */
968     return ret;
969 }
970
971 static void *proxy_html_merge(apr_pool_t *pool, void *BASE, void *ADD)
972 {
973     proxy_html_conf *base = (proxy_html_conf *) BASE;
974     proxy_html_conf *add = (proxy_html_conf *) ADD;
975     proxy_html_conf *conf = apr_palloc(pool, sizeof(proxy_html_conf));
976
977     /* don't merge declarations - just use the most specific */
978     conf->links = (add->links == NULL) ? base->links : add->links;
979     conf->events = (add->events == NULL) ? base->events : add->events;
980
981     conf->charset_out = (add->charset_out == NULL)
982                         ? base->charset_out : add->charset_out;
983
984     if (add->map && base->map) {
985         urlmap *a;
986         conf->map = NULL;
987         for (a = base->map; a; a = a->next) {
988             urlmap *save = conf->map;
989             conf->map = apr_pmemdup(pool, a, sizeof(urlmap));
990             conf->map->next = save;
991         }
992         for (a = add->map; a; a = a->next) {
993             urlmap *save = conf->map;
994             conf->map = apr_pmemdup(pool, a, sizeof(urlmap));
995             conf->map->next = save;
996         }
997     }
998     else
999         conf->map = add->map ? add->map : base->map;
1000
1001     conf->doctype = (add->doctype == DEFAULT_DOCTYPE)
1002                     ? base->doctype : add->doctype;
1003     conf->etag = (add->etag == DEFAULT_ETAG) ? base->etag : add->etag;
1004     conf->bufsz = add->bufsz;
1005     if (add->flags & NORM_RESET) {
1006         conf->flags = add->flags ^ NORM_RESET;
1007         conf->metafix = add->metafix;
1008         conf->extfix = add->extfix;
1009         conf->interp = add->interp;
1010         conf->strip_comments = add->strip_comments;
1011         conf->enabled = add->enabled;
1012     }
1013     else {
1014         conf->flags = base->flags | add->flags;
1015         conf->metafix = base->metafix | add->metafix;
1016         conf->extfix = base->extfix | add->extfix;
1017         conf->interp = base->interp | add->interp;
1018         conf->strip_comments = base->strip_comments | add->strip_comments;
1019         conf->enabled = add->enabled | base->enabled;
1020     }
1021     return conf;
1022 }
1023 #define REGFLAG(n,s,c) ((s&&(ap_strchr_c((s),(c))!=NULL)) ? (n) : 0)
1024 #define XREGFLAG(n,s,c) ((!s||(ap_strchr_c((s),(c))==NULL)) ? (n) : 0)
1025 static const char *comp_urlmap(cmd_parms *cmd, urlmap *newmap,
1026                                const char *from, const char *to,
1027                                const char *flags, const char *cond)
1028 {
1029     const char *err = NULL;
1030     newmap->flags
1031         = XREGFLAG(M_HTML,flags,'h')
1032         | XREGFLAG(M_EVENTS,flags,'e')
1033         | XREGFLAG(M_CDATA,flags,'c')
1034         | REGFLAG(M_ATSTART,flags,'^')
1035         | REGFLAG(M_ATEND,flags,'$')
1036         | REGFLAG(M_REGEX,flags,'R')
1037         | REGFLAG(M_LAST,flags,'L')
1038         | REGFLAG(M_NOTLAST,flags,'l')
1039         | REGFLAG(M_INTERPOLATE_TO,flags,'V')
1040         | REGFLAG(M_INTERPOLATE_FROM,flags,'v');
1041
1042     if ((newmap->flags & M_INTERPOLATE_FROM) || !(newmap->flags & M_REGEX)) {
1043         newmap->from.c = from;
1044         newmap->to = to;
1045     }
1046     else {
1047         newmap->regflags
1048             = REGFLAG(AP_REG_EXTENDED,flags,'x')
1049             | REGFLAG(AP_REG_ICASE,flags,'i')
1050             | REGFLAG(AP_REG_NOSUB,flags,'n')
1051             | REGFLAG(AP_REG_NEWLINE,flags,'s');
1052         newmap->from.r = ap_pregcomp(cmd->pool, from, newmap->regflags);
1053         newmap->to = to;
1054     }
1055     if (cond != NULL) {
1056         /* back-compatibility: support old-style ENV expressions
1057          * by converting to ap_expr syntax.
1058          *
1059          * 1. var --> env(var)
1060          * 2. var=val --> env(var)=val
1061          * 3. !var --> !env(var)
1062          * 4. !var=val --> env(var)!=val
1063          */
1064         char *newcond = NULL;
1065         if (ap_rxplus_exec(cmd->temp_pool, old_expr, cond, &newcond)) {
1066            /* we got a substitution.  Check for the case (3) above
1067             * that the regexp gets wrong: a negation without a comparison.
1068             */
1069             if ((cond[0] == '!') && !ap_strchr_c(cond, '=')) {
1070                 memmove(newcond+1, newcond, strlen(newcond)-1);
1071                 newcond[0] = '!';
1072             }
1073             cond = newcond;
1074         }
1075         newmap->cond = ap_expr_parse_cmd(cmd, cond, 0, &err, NULL);
1076     }
1077     else {
1078         newmap->cond = NULL;
1079     }
1080     return err;
1081 }
1082
1083 static const char *set_urlmap(cmd_parms *cmd, void *CFG, const char *args)
1084 {
1085     proxy_html_conf *cfg = (proxy_html_conf *)CFG;
1086     urlmap *map;
1087     apr_pool_t *pool = cmd->pool;
1088     urlmap *newmap;
1089     const char *usage =
1090               "Usage: ProxyHTMLURLMap from-pattern to-pattern [flags] [cond]";
1091     const char *from;
1092     const char *to;
1093     const char *flags;
1094     const char *cond = NULL;
1095   
1096     if (from = ap_getword_conf(cmd->pool, &args), !from)
1097         return usage;
1098     if (to = ap_getword_conf(cmd->pool, &args), !to)
1099         return usage;
1100     flags = ap_getword_conf(cmd->pool, &args);
1101     if (flags && *flags)
1102         cond = ap_getword_conf(cmd->pool, &args);
1103     if (cond && !*cond)
1104         cond = NULL;
1105
1106     /* the args look OK, so let's use them */
1107     newmap = apr_palloc(pool, sizeof(urlmap));
1108     newmap->next = NULL;
1109     if (cfg->map) {
1110         for (map = cfg->map; map->next; map = map->next);
1111         map->next = newmap;
1112     }
1113     else
1114         cfg->map = newmap;
1115
1116     return comp_urlmap(cmd, newmap, from, to, flags, cond);
1117 }
1118
1119 static const char *set_doctype(cmd_parms *cmd, void *CFG,
1120                                const char *t, const char *l)
1121 {
1122     proxy_html_conf *cfg = (proxy_html_conf *)CFG;
1123     if (!strcasecmp(t, "xhtml")) {
1124         cfg->etag = xhtml_etag;
1125         if (l && !strcasecmp(l, "legacy"))
1126             cfg->doctype = fpi_xhtml_legacy;
1127         else
1128             cfg->doctype = fpi_xhtml;
1129     }
1130     else if (!strcasecmp(t, "html")) {
1131         cfg->etag = html_etag;
1132         if (l && !strcasecmp(l, "legacy"))
1133             cfg->doctype = fpi_html_legacy;
1134         else
1135             cfg->doctype = fpi_html;
1136     }
1137     else if (!strcasecmp(t, "html5")) {
1138         cfg->etag = html_etag;
1139         cfg->doctype = fpi_html5;
1140     }
1141     else {
1142         cfg->doctype = apr_pstrdup(cmd->pool, t);
1143         if (l && ((l[0] == 'x') || (l[0] == 'X')))
1144             cfg->etag = xhtml_etag;
1145         else
1146             cfg->etag = html_etag;
1147     }
1148     return NULL;
1149 }
1150
1151 static const char *set_flags(cmd_parms *cmd, void *CFG, const char *arg)
1152 {
1153     proxy_html_conf *cfg = CFG;
1154     if (arg && *arg) {
1155         if (!strcasecmp(arg, "lowercase"))
1156             cfg->flags |= NORM_LC;
1157         else if (!strcasecmp(arg, "dospath"))
1158             cfg->flags |= NORM_MSSLASH;
1159         else if (!strcasecmp(arg, "reset"))
1160             cfg->flags |= NORM_RESET;
1161     }
1162     return NULL;
1163 }
1164
1165 static const char *set_events(cmd_parms *cmd, void *CFG, const char *arg)
1166 {
1167     tattr *attr;
1168     proxy_html_conf *cfg = CFG;
1169     if (cfg->events == NULL)
1170         cfg->events = apr_array_make(cmd->pool, 20, sizeof(tattr));
1171     attr = apr_array_push(cfg->events);
1172     attr->val = arg;
1173     return NULL;
1174 }
1175
1176 static const char *set_links(cmd_parms *cmd, void *CFG,
1177                              const char *elt, const char *att)
1178 {
1179     apr_array_header_t *attrs;
1180     tattr *attr;
1181     proxy_html_conf *cfg = CFG;
1182
1183     if (cfg->links == NULL)
1184         cfg->links = apr_hash_make(cmd->pool);
1185
1186     attrs = apr_hash_get(cfg->links, elt, APR_HASH_KEY_STRING);
1187     if (!attrs) {
1188         attrs = apr_array_make(cmd->pool, 2, sizeof(tattr*));
1189         apr_hash_set(cfg->links, elt, APR_HASH_KEY_STRING, attrs);
1190     }
1191     attr = apr_array_push(attrs);
1192     attr->val = att;
1193     return NULL;
1194 }
1195 static const command_rec proxy_html_cmds[] = {
1196     AP_INIT_ITERATE("ProxyHTMLEvents", set_events, NULL,
1197                     RSRC_CONF|ACCESS_CONF,
1198                     "Strings to be treated as scripting events"),
1199     AP_INIT_ITERATE2("ProxyHTMLLinks", set_links, NULL,
1200                      RSRC_CONF|ACCESS_CONF, "Declare HTML Attributes"),
1201     AP_INIT_RAW_ARGS("ProxyHTMLURLMap", set_urlmap, NULL,
1202                      RSRC_CONF|ACCESS_CONF, "Map URL From To"),
1203     AP_INIT_TAKE12("ProxyHTMLDoctype", set_doctype, NULL,
1204                    RSRC_CONF|ACCESS_CONF, "(HTML|XHTML) [Legacy]"),
1205     AP_INIT_ITERATE("ProxyHTMLFixups", set_flags, NULL,
1206                     RSRC_CONF|ACCESS_CONF, "Options are lowercase, dospath"),
1207     AP_INIT_FLAG("ProxyHTMLMeta", ap_set_flag_slot,
1208                  (void*)APR_OFFSETOF(proxy_html_conf, metafix),
1209                  RSRC_CONF|ACCESS_CONF, "Fix META http-equiv elements"),
1210     AP_INIT_FLAG("ProxyHTMLInterp", ap_set_flag_slot,
1211                  (void*)APR_OFFSETOF(proxy_html_conf, interp),
1212                  RSRC_CONF|ACCESS_CONF,
1213                  "Support interpolation and conditions in URLMaps"),
1214     AP_INIT_FLAG("ProxyHTMLExtended", ap_set_flag_slot,
1215                  (void*)APR_OFFSETOF(proxy_html_conf, extfix),
1216                  RSRC_CONF|ACCESS_CONF, "Map URLs in Javascript and CSS"),
1217     AP_INIT_FLAG("ProxyHTMLStripComments", ap_set_flag_slot,
1218                  (void*)APR_OFFSETOF(proxy_html_conf, strip_comments),
1219                  RSRC_CONF|ACCESS_CONF, "Strip out comments"),
1220     AP_INIT_TAKE1("ProxyHTMLBufSize", ap_set_int_slot,
1221                   (void*)APR_OFFSETOF(proxy_html_conf, bufsz),
1222                   RSRC_CONF|ACCESS_CONF, "Buffer size"),
1223     AP_INIT_TAKE1("ProxyHTMLCharsetOut", ap_set_string_slot,
1224                   (void*)APR_OFFSETOF(proxy_html_conf, charset_out),
1225                   RSRC_CONF|ACCESS_CONF, "Usage: ProxyHTMLCharsetOut charset"),
1226     AP_INIT_FLAG("ProxyHTMLEnable", ap_set_flag_slot,
1227                  (void*)APR_OFFSETOF(proxy_html_conf, enabled),
1228                  RSRC_CONF|ACCESS_CONF,
1229                  "Enable proxy-html and xml2enc filters"),
1230     { NULL }
1231 };
1232 static int mod_proxy_html(apr_pool_t *p, apr_pool_t *p1, apr_pool_t *p2)
1233 {
1234     seek_meta = ap_pregcomp(p, "<meta[^>]*(http-equiv)[^>]*>",
1235                             AP_REG_EXTENDED|AP_REG_ICASE);
1236     seek_content = apr_strmatch_precompile(p, "content", 0);
1237     memset(&sax, 0, sizeof(htmlSAXHandler));
1238     sax.startElement = pstartElement;
1239     sax.endElement = pendElement;
1240     sax.characters = pcharacters;
1241     sax.comment = pcomment;
1242     sax.cdataBlock = pcdata;
1243     xml2enc_charset = APR_RETRIEVE_OPTIONAL_FN(xml2enc_charset);
1244     xml2enc_filter = APR_RETRIEVE_OPTIONAL_FN(xml2enc_filter);
1245     if (!xml2enc_charset) {
1246         ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, p2, APLOGNO(01425)
1247                       "I18n support in mod_proxy_html requires mod_xml2enc. "
1248                       "Without it, non-ASCII characters in proxied pages are "
1249                       "likely to display incorrectly.");
1250     }
1251
1252     /* old_expr only needs to last the life of the config phase */
1253     old_expr = ap_rxplus_compile(p1, "s/^(!)?(\\w+)((=)(.+))?$/reqenv('$2')$1$4'$5'/");
1254     return OK;
1255 }
1256 static void proxy_html_insert(request_rec *r)
1257 {
1258     proxy_html_conf *cfg;
1259     cfg = ap_get_module_config(r->per_dir_config, &proxy_html_module);
1260     if (cfg->enabled) {
1261         if (xml2enc_filter)
1262             xml2enc_filter(r, NULL, ENCIO_INPUT_CHECKS);
1263         ap_add_output_filter("proxy-html", NULL, r, r->connection);
1264     }
1265 }
1266 static void proxy_html_hooks(apr_pool_t *p)
1267 {
1268     static const char *aszSucc[] = { "mod_filter.c", NULL };
1269     ap_register_output_filter_protocol("proxy-html", proxy_html_filter,
1270                                        NULL, AP_FTYPE_RESOURCE,
1271                           AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH);
1272     /* move this to pre_config so old_expr is available to interpret
1273      * old-style conditions on URL maps.
1274      */
1275     ap_hook_pre_config(mod_proxy_html, NULL, NULL, APR_HOOK_MIDDLE);
1276     ap_hook_insert_filter(proxy_html_insert, NULL, aszSucc, APR_HOOK_MIDDLE);
1277 }
1278
1279 AP_DECLARE_MODULE(proxy_html) = {
1280     STANDARD20_MODULE_STUFF,
1281     proxy_html_config,
1282     proxy_html_merge,
1283     NULL,
1284     NULL,
1285     proxy_html_cmds,
1286     proxy_html_hooks
1287 };