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