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