2.5.2-1.1 release
[manu/mod-proxy-html.git] / mod_proxy_html.c
1 /********************************************************************
2          Copyright (c) 2003-5, WebThing Ltd
3          Author: Nick Kew <nick@webthing.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9      
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18       
19 *********************************************************************/
20
21
22 /********************************************************************
23         Note to Users
24  
25         You are requested to register as a user, at
26         http://apache.webthing.com/registration.html
27  
28         This entitles you to support from the developer.
29         I'm unlikely to reply to help/support requests from
30         non-registered users, unless you're paying and/or offering
31         constructive feedback such as bug reports or sensible
32         suggestions for further development.
33  
34         It also makes a small contribution to the effort
35         that's gone into developing this work.
36 *********************************************************************/
37
38 /* End of Notices */
39
40
41 /*      GO_FASTER
42
43         You can #define GO_FASTER to disable informational logging.
44         This disables the ProxyHTMLLogVerbose option altogether.
45
46         Default is to leave it undefined, and enable verbose logging
47         as a configuration option.  Binaries are supplied with verbose
48         logging enabled.
49 */
50
51 #ifdef GO_FASTER
52 #define VERBOSE(x)
53 #else
54 #define VERBOSE(x) if ( verbose ) x
55 #endif
56
57 #define VERSION_STRING "proxy_html/2.5"
58
59 #include <ctype.h>
60
61 /* libxml */
62 #include <libxml/HTMLparser.h>
63
64 /* apache */
65 #include <http_protocol.h>
66 #include <http_config.h>
67 #include <http_log.h>
68 #include <apr_strings.h>
69
70 /* To support Apache 2.1/2.2, we need the ap_ forms of the
71  * regexp stuff, and they're now used in the code.
72  * To support 2.0 in the same compile, * we #define the
73  * AP_ versions if necessary.
74  */
75 #ifndef AP_REG_ICASE
76 /* it's 2.0, so we #define the ap_ versions */
77 #define ap_regex_t regex_t
78 #define ap_regmatch_t regmatch_t
79 #define AP_REG_EXTENDED REG_EXTENDED
80 #define AP_REG_ICASE REG_ICASE
81 #define AP_REG_NOSUB REG_NOSUB
82 #define AP_REG_NEWLINE REG_NEWLINE
83 #endif
84
85 module AP_MODULE_DECLARE_DATA proxy_html_module ;
86
87 #define M_HTML          0x01
88 #define M_EVENTS        0x02
89 #define M_CDATA         0x04
90 #define M_REGEX         0x08
91 #define M_ATSTART       0x10
92 #define M_ATEND         0x20
93 #define M_LAST          0x40
94
95 typedef struct {
96   unsigned int start ;
97   unsigned int end ;
98 } meta ;
99 typedef struct urlmap {
100   struct urlmap* next ;
101   unsigned int flags ;
102   union {
103     const char* c ;
104     ap_regex_t* r ;
105   } from ;
106   const char* to ;
107 } urlmap ;
108 typedef struct {
109   urlmap* map ;
110   const char* doctype ;
111   const char* etag ;
112   unsigned int flags ;
113   int extfix ;
114   int metafix ;
115   int strip_comments ;
116 #ifndef GO_FASTER
117   int verbose ;
118 #endif
119   size_t bufsz ;
120 } proxy_html_conf ;
121 typedef struct {
122   htmlSAXHandlerPtr sax ;
123   ap_filter_t* f ;
124   proxy_html_conf* cfg ;
125   htmlParserCtxtPtr parser ;
126   apr_bucket_brigade* bb ;
127   char* buf ;
128   size_t offset ;
129   size_t avail ;
130 } saxctxt ;
131
132 static int is_empty_elt(const char* name) {
133   const char** p ;
134   static const char* empty_elts[] = {
135     "br" ,
136     "link" ,
137     "img" ,
138     "hr" ,
139     "input" ,
140     "meta" ,
141     "base" ,
142     "area" ,
143     "param" ,
144     "col" ,
145     "frame" ,
146     "isindex" ,
147     "basefont" ,
148     NULL
149   } ;
150   for ( p = empty_elts ; *p ; ++p )
151     if ( !strcmp( *p, name) )
152       return 1 ;
153   return 0 ;
154 }
155
156 typedef struct {
157         const char* name ;
158         const char** attrs ;
159 } elt_t ;
160
161 #define NORM_LC 0x1
162 #define NORM_MSSLASH 0x2
163 #define NORM_RESET 0x4
164
165 typedef enum { ATTR_IGNORE, ATTR_URI, ATTR_EVENT } rewrite_t ;
166
167 static void normalise(unsigned int flags, char* str) {
168   xmlChar* p ;
169   if ( flags & NORM_LC )
170     for ( p = str ; *p ; ++p )
171       if ( isupper(*p) )
172         *p = tolower(*p) ;
173
174   if ( flags & NORM_MSSLASH )
175     for ( p = strchr(str, '\\') ; p ; p = strchr(p+1, '\\') )
176       *p = '/' ;
177
178 }
179
180 #define FLUSH ap_fwrite(ctx->f->next, ctx->bb, (chars+begin), (i-begin)) ; begin = i+1
181 static void pcharacters(void* ctxt, const xmlChar *chars, int length) {
182   saxctxt* ctx = (saxctxt*) ctxt ;
183   int i ;
184   int begin ;
185   for ( begin=i=0; i<length; i++ ) {
186     switch (chars[i]) {
187       case '&' : FLUSH ; ap_fputs(ctx->f->next, ctx->bb, "&amp;") ; break ;
188       case '<' : FLUSH ; ap_fputs(ctx->f->next, ctx->bb, "&lt;") ; break ;
189       case '>' : FLUSH ; ap_fputs(ctx->f->next, ctx->bb, "&gt;") ; break ;
190       case '"' : FLUSH ; ap_fputs(ctx->f->next, ctx->bb, "&quot;") ; break ;
191       default : break ;
192     }
193   }
194   FLUSH ;
195 }
196 static void preserve(saxctxt* ctx, const size_t len) {
197   char* newbuf ;
198   if ( len <= ( ctx->avail - ctx->offset ) )
199     return ;
200   else while ( len > ( ctx->avail - ctx->offset ) )
201     ctx->avail += ctx->cfg->bufsz ;
202
203   newbuf = realloc(ctx->buf, ctx->avail) ;
204   if ( newbuf != ctx->buf ) {
205     if ( ctx->buf )
206         apr_pool_cleanup_kill(ctx->f->r->pool, ctx->buf, (void*)free) ;
207     apr_pool_cleanup_register(ctx->f->r->pool, newbuf,
208         (void*)free, apr_pool_cleanup_null);
209     ctx->buf = newbuf ;
210   }
211 }
212 static void pappend(saxctxt* ctx, const char* buf, const size_t len) {
213   preserve(ctx, len) ;
214   memcpy(ctx->buf+ctx->offset, buf, len) ;
215   ctx->offset += len ;
216 }
217 static void dump_content(saxctxt* ctx) {
218   urlmap* m ;
219   char* found ;
220   size_t s_from, s_to ;
221   size_t match ;
222   char c = 0 ;
223   int nmatch ;
224   ap_regmatch_t pmatch[10] ;
225   char* subs ;
226   size_t len, offs ;
227 #ifndef GO_FASTER
228   int verbose = ctx->cfg->verbose ;
229 #endif
230
231   pappend(ctx, &c, 1) ; /* append null byte */
232         /* parse the text for URLs */
233   for ( m = ctx->cfg->map ; m ; m = m->next ) {
234     if ( ! ( m->flags & M_CDATA ) )
235         continue ;
236     if ( m->flags & M_REGEX ) {
237       nmatch = 10 ;
238       offs = 0 ;
239       while ( ! ap_regexec(m->from.r, ctx->buf+offs, nmatch, pmatch, 0) ) {
240         match = pmatch[0].rm_so ;
241         s_from = pmatch[0].rm_eo - match ;
242         subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
243                 nmatch, pmatch) ;
244         s_to = strlen(subs) ;
245         len = strlen(ctx->buf) ;
246         offs += match ;
247         VERBOSE( {
248           const char* f = apr_pstrndup(ctx->f->r->pool,
249                 ctx->buf + offs , s_from ) ;
250           ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, ctx->f->r,
251                 "C/RX: match at %s, substituting %s", f, subs) ;
252         } )
253         if ( s_to > s_from) {
254           preserve(ctx, s_to - s_from) ;
255           memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
256                 len + 1 - s_from - offs) ;
257           memcpy(ctx->buf+offs, subs, s_to) ;
258         } else {
259           memcpy(ctx->buf + offs, subs, s_to) ;
260           memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
261                 len + 1 - s_from - offs) ;
262         }
263         offs += s_to ;
264       }
265     } else {
266       s_from = strlen(m->from.c) ;
267       s_to = strlen(m->to) ;
268       for ( found = strstr(ctx->buf, m->from.c) ; found ;
269                 found = strstr(ctx->buf+match+s_to, m->from.c) ) {
270         match = found - ctx->buf ;
271         if ( ( m->flags & M_ATSTART ) && ( match != 0) )
272           break ;
273         len = strlen(ctx->buf) ;
274         if ( ( m->flags & M_ATEND ) && ( match < (len - s_from) ) )
275           continue ;
276         VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, ctx->f->r,
277             "C: matched %s, substituting %s", m->from.c, m->to) ) ;
278         if ( s_to > s_from ) {
279           preserve(ctx, s_to - s_from) ;
280           memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
281                 len + 1 - s_from - match) ;
282           memcpy(ctx->buf+match, m->to, s_to) ;
283         } else {
284           memcpy(ctx->buf+match, m->to, s_to) ;
285           memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
286                 len + 1 - s_from - match) ;
287         }
288       }
289     }
290   }
291   ap_fputs(ctx->f->next, ctx->bb, ctx->buf) ;
292 }
293 static void pcdata(void* ctxt, const xmlChar *chars, int length) {
294   saxctxt* ctx = (saxctxt*) ctxt ;
295   if ( ctx->cfg->extfix ) {
296     pappend(ctx, chars, length) ;
297   } else {
298     ap_fwrite(ctx->f->next, ctx->bb, chars, length) ;
299   }
300 }
301 static void pcomment(void* ctxt, const xmlChar *chars) {
302   saxctxt* ctx = (saxctxt*) ctxt ;
303   if ( ctx->cfg->strip_comments )
304     return ;
305
306   if ( ctx->cfg->extfix ) {
307     pappend(ctx, "<!--", 4) ;
308     pappend(ctx, chars, strlen(chars) ) ;
309     pappend(ctx, "-->", 3) ;
310   } else {
311     ap_fputstrs(ctx->f->next, ctx->bb, "<!--", chars, "-->", NULL) ;
312   }
313 }
314 static void pendElement(void* ctxt, const xmlChar* name) {
315   saxctxt* ctx = (saxctxt*) ctxt ;
316   if ( ctx->offset > 0 ) {
317     dump_content(ctx) ;
318     ctx->offset = 0 ;   /* having dumped it, we can re-use the memory */
319   }
320   if ( ! is_empty_elt(name) )
321     ap_fprintf(ctx->f->next, ctx->bb, "</%s>", name) ;
322 }
323 static void pstartElement(void* ctxt, const xmlChar* name,
324                 const xmlChar** attrs ) {
325
326   int num_match ;
327   size_t offs, len ;
328   char* subs ;
329   rewrite_t is_uri ;
330   const char** linkattrs ;
331   const xmlChar** a ;
332   const elt_t* elt ;
333   const char** linkattr ;
334   urlmap* m ;
335   size_t s_to, s_from, match ;
336   char* found ;
337   saxctxt* ctx = (saxctxt*) ctxt ;
338   size_t nmatch ;
339   ap_regmatch_t pmatch[10] ;
340 #ifndef GO_FASTER
341   int verbose = ctx->cfg->verbose ;
342 #endif
343
344   static const char* href[] = { "href", NULL } ;
345   static const char* cite[] = { "cite", NULL } ;
346   static const char* action[] = { "action", NULL } ;
347   static const char* imgattr[] = { "src", "longdesc", "usemap", NULL } ;
348   static const char* inputattr[] = { "src", "usemap", NULL } ;
349   static const char* scriptattr[] = { "src", "for", NULL } ;
350   static const char* frameattr[] = { "src", "longdesc", NULL } ;
351   static const char* objattr[] =
352                        { "classid", "codebase", "data", "usemap", NULL } ;
353   static const char* profile[] = { "profile", NULL } ;
354   static const char* background[] = { "background", NULL } ;
355   static const char* codebase[] = { "codebase", NULL } ;
356
357   static const elt_t linked_elts[] = {
358     { "a" , href } ,
359     { "img" , imgattr } ,
360     { "form", action } ,
361     { "link" , href } ,
362     { "script" , scriptattr } ,
363     { "base" , href } ,
364     { "area" , href } ,
365     { "input" , inputattr } ,
366     { "frame", frameattr } ,
367     { "iframe", frameattr } ,
368     { "object", objattr } ,
369     { "q" , cite } ,
370     { "blockquote" , cite } ,
371     { "ins" , cite } ,
372     { "del" , cite } ,
373     { "head" , profile } ,
374     { "body" , background } ,
375     { "applet", codebase } ,
376     { NULL, NULL }
377   } ;
378   static const char* events[] = {
379         "onclick" ,
380         "ondblclick" ,
381         "onmousedown" ,
382         "onmouseup" ,
383         "onmouseover" ,
384         "onmousemove" ,
385         "onmouseout" ,
386         "onkeypress" ,
387         "onkeydown" ,
388         "onkeyup" ,
389         "onfocus" ,
390         "onblur" ,
391         "onload" ,
392         "onunload" ,
393         "onsubmit" ,
394         "onreset" ,
395         "onselect" ,
396         "onchange" ,
397         NULL
398   } ;
399
400   ap_fputc(ctx->f->next, ctx->bb, '<') ;
401   ap_fputs(ctx->f->next, ctx->bb, name) ;
402
403   if ( attrs ) {
404     linkattrs = 0 ;
405     for ( elt = linked_elts;  elt->name != NULL ; ++elt )
406       if ( !strcmp(elt->name, name) ) {
407         linkattrs = elt->attrs ;
408         break ;
409       }
410     for ( a = attrs ; *a ; a += 2 ) {
411       ctx->offset = 0 ;
412       if ( a[1] ) {
413         pappend(ctx, a[1], strlen(a[1])+1) ;
414         is_uri = ATTR_IGNORE ;
415         if ( linkattrs ) {
416           for ( linkattr = linkattrs ; *linkattr ; ++linkattr) {
417             if ( !strcmp(*linkattr, *a) ) {
418               is_uri = ATTR_URI ;
419               break ;
420             }
421           }
422         }
423         if ( (is_uri == ATTR_IGNORE) && ctx->cfg->extfix ) {
424           for ( linkattr = events; *linkattr; ++linkattr ) {
425             if ( !strcmp(*linkattr, *a) ) {
426               is_uri = ATTR_EVENT ;
427               break ;
428             }
429           }
430         }
431         switch ( is_uri ) {
432           case ATTR_URI:
433             num_match = 0 ;
434             for ( m = ctx->cfg->map ; m ; m = m->next ) {
435               if ( ! ( m->flags & M_HTML ) )
436                 continue ;
437               if ( m->flags & M_REGEX ) {
438                 nmatch = 10 ;
439                 if ( ! ap_regexec(m->from.r, ctx->buf, nmatch, pmatch, 0) ) {
440                   ++num_match ;
441                   offs = match = pmatch[0].rm_so ;
442                   s_from = pmatch[0].rm_eo - match ;
443                   subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
444                         nmatch, pmatch) ;
445                   VERBOSE( {
446                     const char* f = apr_pstrndup(ctx->f->r->pool,
447                         ctx->buf + offs , s_from ) ;
448                     ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, ctx->f->r,
449                         "H/RX: match at %s, substituting %s", f, subs) ;
450                   } )
451                   s_to = strlen(subs) ;
452                   len = strlen(ctx->buf) ;
453                   if ( s_to > s_from) {
454                     preserve(ctx, s_to - s_from) ;
455                     memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
456                         len + 1 - s_from - offs) ;
457                     memcpy(ctx->buf+offs, subs, s_to) ;
458                   } else {
459                     memcpy(ctx->buf + offs, subs, s_to) ;
460                     memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
461                         len + 1 - s_from - offs) ;
462                   }
463                 }
464               } else {
465                 s_from = strlen(m->from.c) ;
466                 if ( ! strncasecmp(ctx->buf, m->from.c, s_from ) ) {
467                   ++num_match ;
468                   s_to = strlen(m->to) ;
469                   len = strlen(ctx->buf) ;
470                   VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, ctx->f->r,
471                     "H: matched %s, substituting %s", m->from.c, m->to) ) ;
472                   if ( s_to > s_from ) {
473                     preserve(ctx, s_to - s_from) ;
474                     memmove(ctx->buf+s_to, ctx->buf+s_from,
475                         len + 1 - s_from ) ;
476                     memcpy(ctx->buf, m->to, s_to) ;
477                   } else {      /* it fits in the existing space */
478                     memcpy(ctx->buf, m->to, s_to) ;
479                     memmove(ctx->buf+s_to, ctx->buf+s_from,
480                         len + 1 - s_from) ;
481                   }
482                   break ;
483                 }
484               }
485               if ( num_match > 0 )      /* URIs only want one match */
486                 break ;
487             }
488             break ;
489           case ATTR_EVENT:
490             for ( m = ctx->cfg->map ; m ; m = m->next ) {
491               num_match = 0 ;   /* reset here since we're working per-rule */
492               if ( ! ( m->flags & M_EVENTS ) )
493                 continue ;
494               if ( m->flags & M_REGEX ) {
495                 nmatch = 10 ;
496                 offs = 0 ;
497                 while ( ! ap_regexec(m->from.r, ctx->buf+offs,
498                         nmatch, pmatch, 0) ) {
499                   match = pmatch[0].rm_so ;
500                   s_from = pmatch[0].rm_eo - match ;
501                   subs = ap_pregsub(ctx->f->r->pool, m->to, ctx->buf+offs,
502                         nmatch, pmatch) ;
503                   VERBOSE( {
504                     const char* f = apr_pstrndup(ctx->f->r->pool,
505                         ctx->buf + offs , s_from ) ;
506                     ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, ctx->f->r,
507                         "E/RX: match at %s, substituting %s", f, subs) ;
508                   } )
509                   s_to = strlen(subs) ;
510                   offs += match ;
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, ctx->buf+offs+s_from,
515                         len + 1 - s_from - offs) ;
516                     memcpy(ctx->buf+offs, subs, s_to) ;
517                   } else {
518                     memcpy(ctx->buf + offs, subs, s_to) ;
519                     memmove(ctx->buf+offs+s_to, ctx->buf+offs+s_from,
520                         len + 1 - s_from - offs) ;
521                   }
522                   offs += s_to ;
523                   ++num_match ;
524                 }
525               } else {
526                 found = strstr(ctx->buf, m->from.c) ;
527                 if ( (m->flags & M_ATSTART) && ( found != ctx->buf) )
528                   continue ;
529                 while ( found ) {
530                   s_from = strlen(m->from.c) ;
531                   s_to = strlen(m->to) ;
532                   match = found - ctx->buf ;
533                   if ( ( s_from < strlen(found) ) && (m->flags & M_ATEND ) ) {
534                     found = strstr(ctx->buf+match+s_from, m->from.c) ;
535                     continue ;
536                   } else {
537                     found = strstr(ctx->buf+match+s_to, m->from.c) ;
538                   }
539                   VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, ctx->f->r,
540                     "E: matched %s, substituting %s", m->from.c, m->to) ) ;
541                   len = strlen(ctx->buf) ;
542                   if ( s_to > s_from ) {
543                     preserve(ctx, s_to - s_from) ;
544                     memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
545                         len + 1 - s_from - match) ;
546                     memcpy(ctx->buf+match, m->to, s_to) ;
547                   } else {
548                     memcpy(ctx->buf+match, m->to, s_to) ;
549                     memmove(ctx->buf+match+s_to, ctx->buf+match+s_from,
550                         len + 1 - s_from - match) ;
551                   }
552                   ++num_match ;
553                 }
554               }
555               if ( num_match && ( m->flags & M_LAST ) )
556                 break ;
557             }
558             break ;
559           case ATTR_IGNORE:
560             break ;
561         }
562       }
563       if ( ! a[1] )
564         ap_fputstrs(ctx->f->next, ctx->bb, " ", a[0], NULL) ;
565       else {
566
567         if ( ctx->cfg->flags != 0 )
568           normalise(ctx->cfg->flags, ctx->buf) ;
569
570         /* write the attribute, using pcharacters to html-escape
571            anything that needs it in the value.
572         */
573         ap_fputstrs(ctx->f->next, ctx->bb, " ", a[0], "=\"", NULL) ;
574         pcharacters(ctx, ctx->buf, strlen(ctx->buf)) ;
575         ap_fputc(ctx->f->next, ctx->bb, '"') ;
576       }
577     }
578   }
579   ctx->offset = 0 ;
580   if ( is_empty_elt(name) )
581     ap_fputs(ctx->f->next, ctx->bb, ctx->cfg->etag) ;
582   else
583     ap_fputc(ctx->f->next, ctx->bb, '>') ;
584 }
585 static htmlSAXHandlerPtr setupSAX(apr_pool_t* pool) {
586   htmlSAXHandlerPtr sax = apr_pcalloc(pool, sizeof(htmlSAXHandler) ) ;
587   sax->startDocument = NULL ;
588   sax->endDocument = NULL ;
589   sax->startElement = pstartElement ;
590   sax->endElement = pendElement ;
591   sax->characters = pcharacters ;
592   sax->comment = pcomment ;
593   sax->cdataBlock = pcdata ;
594   return sax ;
595 }
596
597 static ap_regex_t* seek_meta_ctype ;
598 static ap_regex_t* seek_charset ;
599 static ap_regex_t* seek_meta ;
600
601 static void proxy_html_child_init(apr_pool_t* pool, server_rec* s) {
602   seek_meta_ctype = ap_pregcomp(pool,
603         "(<meta[^>]*http-equiv[ \t\r\n='\"]*content-type[^>]*>)",
604         AP_REG_EXTENDED|AP_REG_ICASE) ;
605   seek_charset = ap_pregcomp(pool, "charset=([A-Za-z0-9_-]+)",
606         AP_REG_EXTENDED|AP_REG_ICASE) ;
607   seek_meta = ap_pregcomp(pool, "<meta[^>]*(http-equiv)[^>]*>",
608         AP_REG_EXTENDED|AP_REG_ICASE) ;
609 }
610
611 static xmlCharEncoding sniff_encoding(
612                 request_rec* r, const char* cbuf, size_t bytes
613 #ifndef GO_FASTER
614                         , int verbose
615 #endif
616         ) {
617   xmlCharEncoding ret ;
618   char* encoding = NULL ;
619   char* p ;
620   ap_regmatch_t match[2] ;
621   unsigned char* buf = (unsigned char*)cbuf ;
622
623   VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
624                 "Content-Type is %s", r->content_type) ) ;
625
626 /* If we've got it in the HTTP headers, there's nothing to do */
627   if ( r->content_type &&
628         ( p = ap_strcasestr(r->content_type, "charset=") , p > 0 ) ) {
629     p += 8 ;
630     if ( encoding = apr_pstrndup(r->pool, p, strcspn(p, " ;") ) , encoding ) {
631       if ( ret = xmlParseCharEncoding(encoding),
632                 ret != XML_CHAR_ENCODING_ERROR ) {
633         VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
634                 "Got charset %s from HTTP headers", encoding) ) ;
635         return ret ;
636       } else {
637         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
638                 "Unsupported charset %s in HTTP headers", encoding) ;
639         encoding = NULL ;
640       }
641     }
642   }
643
644 /* to sniff, first we look for BOM */
645   if ( ret = xmlDetectCharEncoding(buf, bytes),
646         ret != XML_CHAR_ENCODING_NONE ) {
647     VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
648         "Got charset from XML rules.") ) ;
649     return ret ;
650   }
651
652 /* If none of the above, look for a META-thingey */
653   encoding = NULL ;
654   if ( ap_regexec(seek_meta_ctype, buf, 1, match, 0) == 0 ) {
655     p = apr_pstrndup(r->pool, buf + match[0].rm_so,
656         match[0].rm_eo - match[0].rm_so) ;
657     if ( ap_regexec(seek_charset, p, 2, match, 0) == 0 )
658       encoding = apr_pstrndup(r->pool, p+match[1].rm_so,
659         match[1].rm_eo - match[1].rm_so) ;
660   }
661
662 /* either it's set to something we found or it's still the default */
663   if ( encoding ) {
664     if ( ret = xmlParseCharEncoding(encoding),
665         ret != XML_CHAR_ENCODING_ERROR ) {
666       VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
667         "Got charset %s from HTML META", encoding) ) ;
668       return ret ;
669     } else {
670       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
671         "Unsupported charset %s in HTML META", encoding) ;
672     }
673   }
674 /* the old HTTP default is a last resort */
675   ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r,
676         "No usable charset information: using old HTTP default LATIN1") ;
677   return XML_CHAR_ENCODING_8859_1 ;
678 }
679 static meta* metafix(request_rec* r, const char* buf /*, size_t bytes*/
680 #ifndef GO_FASTER
681                 , int verbose
682 #endif
683         ) {
684   meta* ret = NULL ;
685   size_t offs = 0 ;
686   const char* p ;
687   const char* q ;
688   char* header ;
689   char* content ;
690   ap_regmatch_t pmatch[2] ;
691   char delim ;
692
693   while ( ! ap_regexec(seek_meta, buf+offs, 2, pmatch, 0) ) {
694     header = NULL ;
695     content = NULL ;
696     p = buf+offs+pmatch[1].rm_eo ;
697     while ( !isalpha(*++p) ) ;
698     for ( q = p ; isalnum(*q) || (*q == '-') ; ++q ) ;
699     header = apr_pstrndup(r->pool, p, q-p) ;
700     if ( strncasecmp(header, "Content-", 8) ) {
701 /* find content=... string */
702       for ( p = ap_strstr((char*)buf+offs+pmatch[0].rm_so, "content") ; *p ; ) {
703         p += 7 ;
704         while ( *p && isspace(*p) )
705           ++p ;
706         if ( *p != '=' )
707           continue ;
708         while ( *p && isspace(*++p) ) ;
709         if ( ( *p == '\'' ) || ( *p == '"' ) ) {
710           delim = *p++ ;
711           for ( q = p ; *q != delim ; ++q ) ;
712         } else {
713           for ( q = p ; *q && !isspace(*q) && (*q != '>') ; ++q ) ;
714         }
715         content = apr_pstrndup(r->pool, p, q-p) ;
716         break ;
717       }
718     } else if ( !strncasecmp(header, "Content-Type", 12) ) {
719       ret = apr_palloc(r->pool, sizeof(meta) ) ;
720       ret->start = pmatch[0].rm_so ;
721       ret->end = pmatch[0].rm_eo ;
722     }
723     if ( header && content ) {
724       VERBOSE( ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
725         "Adding header [%s: %s] from HTML META", header, content) ) ; 
726       apr_table_setn(r->headers_out, header, content) ;
727     }
728     offs += pmatch[0].rm_eo ;
729   }
730   return ret ;
731 }
732
733 static int proxy_html_filter_init(ap_filter_t* f) {
734   const char* env ;
735   saxctxt* fctx ;
736
737 #if 0
738 /* remove content-length filter */
739   ap_filter_rec_t* clf = ap_get_output_filter_handle("CONTENT_LENGTH") ;
740   ap_filter_t* ff = f->next ;
741
742   do {
743     ap_filter_t* fnext = ff->next ;
744     if ( ff->frec == clf )
745       ap_remove_output_filter(ff) ;
746     ff = fnext ;
747   } while ( ff ) ;
748 #endif
749
750   fctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(saxctxt)) ;
751   fctx->sax = setupSAX(f->r->pool) ;
752   fctx->f = f ;
753   fctx->bb = apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc) ;
754   fctx->cfg = ap_get_module_config(f->r->per_dir_config,&proxy_html_module);
755
756   if ( f->r->proto_num >= 1001 ) {
757     if ( ! f->r->main && ! f->r->prev ) {
758       env = apr_table_get(f->r->subprocess_env, "force-response-1.0") ;
759       if ( !env )
760         f->r->chunked = 1 ;
761     }
762   }
763
764   apr_table_unset(f->r->headers_out, "Content-Length") ;
765   apr_table_unset(f->r->headers_out, "ETag") ;
766   return OK ;
767 }
768 static saxctxt* check_filter_init (ap_filter_t* f) {
769
770   const char* errmsg = NULL ;
771   if ( ! f->r->proxyreq ) {
772     errmsg = "Non-proxy request; not inserting proxy-html filter" ;
773   } else if ( ! f->r->content_type ) {
774     errmsg = "No content-type; bailing out of proxy-html filter" ;
775   } else if ( strncasecmp(f->r->content_type, "text/html", 9) &&
776         strncasecmp(f->r->content_type, "application/xhtml+xml", 21) ) {
777     errmsg = "Non-HTML content; not inserting proxy-html filter" ;
778   }
779
780   if ( errmsg ) {
781 #ifndef GO_FASTER
782     proxy_html_conf* cfg
783         = ap_get_module_config(f->r->per_dir_config, &proxy_html_module);
784     if ( cfg->verbose ) {
785       ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, errmsg) ;
786     }
787 #endif
788     ap_remove_output_filter(f) ;
789     return NULL ;
790   }
791   if ( ! f->ctx )
792     proxy_html_filter_init(f) ;
793   return f->ctx ;
794 }
795 static int proxy_html_filter(ap_filter_t* f, apr_bucket_brigade* bb) {
796   apr_bucket* b ;
797   meta* m = NULL ;
798   xmlCharEncoding enc ;
799   const char* buf = 0 ;
800   apr_size_t bytes = 0 ;
801 #ifndef USE_OLD_LIBXML2
802   int xmlopts = XML_PARSE_RECOVER | XML_PARSE_NONET |
803         XML_PARSE_NOBLANKS | XML_PARSE_NOERROR | XML_PARSE_NOWARNING ;
804 #endif
805
806   saxctxt* ctxt = check_filter_init(f) ;
807   if ( ! ctxt )
808     return ap_pass_brigade(f->next, bb) ;
809
810   for ( b = APR_BRIGADE_FIRST(bb) ;
811         b != APR_BRIGADE_SENTINEL(bb) ;
812         b = APR_BUCKET_NEXT(b) ) {
813     if ( APR_BUCKET_IS_EOS(b) ) {
814       if ( ctxt->parser != NULL ) {
815         htmlParseChunk(ctxt->parser, buf, 0, 1) ;
816       }
817       APR_BRIGADE_INSERT_TAIL(ctxt->bb,
818         apr_bucket_eos_create(ctxt->bb->bucket_alloc) ) ;
819       ap_pass_brigade(ctxt->f->next, ctxt->bb) ;
820     } else if ( ! APR_BUCKET_IS_METADATA(b) &&
821                 apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
822               == APR_SUCCESS ) {
823       if ( ctxt->parser == NULL ) {
824         if ( buf && buf[bytes] != 0 ) {
825           /* make a string for parse routines to play with */
826           char* buf1 = apr_palloc(f->r->pool, bytes+1) ;
827           memcpy(buf1, buf, bytes) ;
828           buf1[bytes] = 0 ;
829           buf = buf1 ;
830         }
831 #ifndef GO_FASTER
832         enc = sniff_encoding(f->r, buf, bytes, ctxt->cfg->verbose) ;
833         if ( ctxt->cfg->metafix )
834           m = metafix(f->r, buf, ctxt->cfg->verbose) ;
835 #else
836         enc = sniff_encoding(f->r, buf, bytes) ;
837         if ( ctxt->cfg->metafix )
838           m = metafix(f->r, buf) ;
839 #endif
840         ap_set_content_type(f->r, "text/html;charset=utf-8") ;
841         ap_fputs(f->next, ctxt->bb, ctxt->cfg->doctype) ;
842         if ( m ) {
843           ctxt->parser = htmlCreatePushParserCtxt(ctxt->sax, ctxt,
844                 buf, m->start, 0, enc ) ;
845           htmlParseChunk(ctxt->parser, buf+m->end, bytes-m->end, 0) ;
846         } else {
847           ctxt->parser = htmlCreatePushParserCtxt(ctxt->sax, ctxt,
848                 buf, bytes, 0, enc ) ;
849         }
850         apr_pool_cleanup_register(f->r->pool, ctxt->parser,
851                 (void*)htmlFreeParserCtxt, apr_pool_cleanup_null) ;
852 #ifndef USE_OLD_LIBXML2
853         if ( xmlopts = xmlCtxtUseOptions(ctxt->parser, xmlopts ), xmlopts )
854           ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, f->r,
855                 "Unsupported parser opts %x", xmlopts) ;
856 #endif
857       } else {
858         htmlParseChunk(ctxt->parser, buf, bytes, 0) ;
859       }
860     } else {
861       ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, "Error in bucket read") ;
862     }
863   }
864   /*ap_fflush(ctxt->f->next, ctxt->bb) ;        // uncomment for debug */
865   apr_brigade_cleanup(bb) ;
866   return APR_SUCCESS ;
867 }
868 static const char* fpi_html =
869         "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n" ;
870 static const char* fpi_html_legacy =
871         "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" ;
872 static const char* fpi_xhtml =
873         "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" ;
874 static const char* fpi_xhtml_legacy =
875         "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" ;
876 static const char* html_etag = ">" ;
877 static const char* xhtml_etag = " />" ;
878 /*#define DEFAULT_DOCTYPE fpi_html */
879 static const char* DEFAULT_DOCTYPE = "" ;
880 #define DEFAULT_ETAG html_etag
881
882 static void* proxy_html_config(apr_pool_t* pool, char* x) {
883   proxy_html_conf* ret = apr_pcalloc(pool, sizeof(proxy_html_conf) ) ;
884   ret->doctype = DEFAULT_DOCTYPE ;
885   ret->etag = DEFAULT_ETAG ;
886   ret->bufsz = 8192 ;
887   return ret ;
888 }
889 static void* proxy_html_merge(apr_pool_t* pool, void* BASE, void* ADD) {
890   proxy_html_conf* base = (proxy_html_conf*) BASE ;
891   proxy_html_conf* add = (proxy_html_conf*) ADD ;
892   proxy_html_conf* conf = apr_palloc(pool, sizeof(proxy_html_conf)) ;
893
894   if ( add->map && base->map ) {
895     urlmap* a ;
896     conf->map = NULL ;
897     for ( a = base->map ; a ; a = a->next ) {
898       urlmap* save = conf->map ;
899       conf->map = apr_pmemdup(pool, a, sizeof(urlmap)) ;
900       conf->map->next = save ;
901     }
902     for ( a = add->map ; a ; a = a->next ) {
903       urlmap* save = conf->map ;
904       conf->map = apr_pmemdup(pool, a, sizeof(urlmap)) ;
905       conf->map->next = save ;
906     }
907   } else
908     conf->map = add->map ? add->map : base->map ;
909
910   conf->doctype = ( add->doctype == DEFAULT_DOCTYPE )
911                 ? base->doctype : add->doctype ;
912   conf->etag = ( add->etag == DEFAULT_ETAG ) ? base->etag : add->etag ;
913   conf->bufsz = add->bufsz ;
914   if ( add->flags & NORM_RESET ) {
915     conf->flags = add->flags ^ NORM_RESET ;
916     conf->metafix = add->metafix ;
917     conf->extfix = add->extfix ;
918     conf->strip_comments = add->strip_comments ;
919 #ifndef GO_FASTER
920     conf->verbose = add->verbose ;
921 #endif
922   } else {
923     conf->flags = base->flags | add->flags ;
924     conf->metafix = base->metafix | add->metafix ;
925     conf->extfix = base->extfix | add->extfix ;
926     conf->strip_comments = base->strip_comments | add->strip_comments ;
927 #ifndef GO_FASTER
928     conf->verbose = base->verbose | add->verbose ;
929 #endif
930   }
931   return conf ;
932 }
933 #define REGFLAG(n,s,c) ( (s&&(ap_strchr((char*)(s),(c))!=NULL)) ? (n) : 0 )
934 #define XREGFLAG(n,s,c) ( (!s||(ap_strchr((char*)(s),(c))==NULL)) ? (n) : 0 )
935 static const char* set_urlmap(cmd_parms* cmd, void* CFG,
936         const char* from, const char* to, const char* flags) {
937   int regflags ;
938   proxy_html_conf* cfg = (proxy_html_conf*)CFG ;
939   urlmap* map ;
940   urlmap* newmap = apr_palloc(cmd->pool, sizeof(urlmap) ) ;
941
942   newmap->next = NULL ;
943   newmap->flags
944         = XREGFLAG(M_HTML,flags,'h')
945         | XREGFLAG(M_EVENTS,flags,'e')
946         | XREGFLAG(M_CDATA,flags,'c')
947         | REGFLAG(M_ATSTART,flags,'^')
948         | REGFLAG(M_ATEND,flags,'$')
949         | REGFLAG(M_REGEX,flags,'R')
950         | REGFLAG(M_LAST,flags,'L')
951   ;
952
953   if ( cfg->map ) {
954     for ( map = cfg->map ; map->next ; map = map->next ) ;
955     map->next = newmap ;
956   } else
957     cfg->map = newmap ;
958
959   if ( ! (newmap->flags & M_REGEX) ) {
960     newmap->from.c = apr_pstrdup(cmd->pool, from) ;
961     newmap->to = apr_pstrdup(cmd->pool, to) ;
962   } else {
963     regflags
964         = REGFLAG(AP_REG_EXTENDED,flags,'x')
965         | REGFLAG(AP_REG_ICASE,flags,'i')
966         | REGFLAG(AP_REG_NOSUB,flags,'n')
967         | REGFLAG(AP_REG_NEWLINE,flags,'s')
968     ;
969     newmap->from.r = ap_pregcomp(cmd->pool, from, regflags) ;
970     newmap->to = apr_pstrdup(cmd->pool, to) ;
971   }
972   return NULL ;
973 }
974 static const char* set_doctype(cmd_parms* cmd, void* CFG, const char* t,
975         const char* l) {
976   proxy_html_conf* cfg = (proxy_html_conf*)CFG ;
977   if ( !strcasecmp(t, "xhtml") ) {
978     cfg->etag = xhtml_etag ;
979     if ( l && !strcasecmp(l, "legacy") )
980       cfg->doctype = fpi_xhtml_legacy ;
981     else
982       cfg->doctype = fpi_xhtml ;
983   } else if ( !strcasecmp(t, "html") ) {
984     cfg->etag = html_etag ;
985     if ( l && !strcasecmp(l, "legacy") )
986       cfg->doctype = fpi_html_legacy ;
987     else
988       cfg->doctype = fpi_html ;
989   } else {
990     cfg->doctype = apr_pstrdup(cmd->pool, t) ;
991     if ( l && ( ( l[0] == 'x' ) || ( l[0] == 'X' ) ) )
992       cfg->etag = xhtml_etag ;
993     else
994       cfg->etag = html_etag ;
995   }
996   return NULL ;
997 }
998 static void set_param(proxy_html_conf* cfg, const char* arg) {
999   if ( arg && *arg ) {
1000     if ( !strcmp(arg, "lowercase") )
1001       cfg->flags |= NORM_LC ;
1002     else if ( !strcmp(arg, "dospath") )
1003       cfg->flags |= NORM_MSSLASH ;
1004     else if ( !strcmp(arg, "reset") )
1005       cfg->flags |= NORM_RESET ;
1006   }
1007 }
1008 static const char* set_flags(cmd_parms* cmd, void* CFG, const char* arg1,
1009         const char* arg2, const char* arg3) {
1010   set_param( (proxy_html_conf*)CFG, arg1) ;
1011   set_param( (proxy_html_conf*)CFG, arg2) ;
1012   set_param( (proxy_html_conf*)CFG, arg3) ;
1013   return NULL ;
1014 }
1015 static const command_rec proxy_html_cmds[] = {
1016   AP_INIT_TAKE23("ProxyHTMLURLMap", set_urlmap, NULL,
1017         RSRC_CONF|ACCESS_CONF, "Map URL From To" ) ,
1018   AP_INIT_TAKE12("ProxyHTMLDoctype", set_doctype, NULL,
1019         RSRC_CONF|ACCESS_CONF, "(HTML|XHTML) [Legacy]" ) ,
1020   AP_INIT_TAKE123("ProxyHTMLFixups", set_flags, NULL,
1021         RSRC_CONF|ACCESS_CONF, "Options are lowercase, dospath" ) ,
1022   AP_INIT_FLAG("ProxyHTMLMeta", ap_set_flag_slot,
1023         (void*)APR_OFFSETOF(proxy_html_conf, metafix),
1024         RSRC_CONF|ACCESS_CONF, "Fix META http-equiv elements" ) ,
1025   AP_INIT_FLAG("ProxyHTMLExtended", ap_set_flag_slot,
1026         (void*)APR_OFFSETOF(proxy_html_conf, extfix),
1027         RSRC_CONF|ACCESS_CONF, "Map URLs in Javascript and CSS" ) ,
1028   AP_INIT_FLAG("ProxyHTMLStripComments", ap_set_flag_slot,
1029         (void*)APR_OFFSETOF(proxy_html_conf, strip_comments),
1030         RSRC_CONF|ACCESS_CONF, "Strip out comments" ) ,
1031 #ifndef GO_FASTER
1032   AP_INIT_FLAG("ProxyHTMLLogVerbose", ap_set_flag_slot,
1033         (void*)APR_OFFSETOF(proxy_html_conf, verbose),
1034         RSRC_CONF|ACCESS_CONF, "Verbose Logging (use with LogLevel Info)" ) ,
1035 #endif
1036   AP_INIT_TAKE1("ProxyHTMLBufSize", ap_set_int_slot,
1037         (void*)APR_OFFSETOF(proxy_html_conf, bufsz),
1038         RSRC_CONF|ACCESS_CONF, "Buffer size" ) ,
1039   { NULL }
1040 } ;
1041 static int mod_proxy_html(apr_pool_t* p, apr_pool_t* p1, apr_pool_t* p2,
1042         server_rec* s) {
1043   ap_add_version_component(p, VERSION_STRING) ;
1044   return OK ;
1045 }
1046 static void proxy_html_hooks(apr_pool_t* p) {
1047   ap_register_output_filter("proxy-html", proxy_html_filter,
1048         NULL, AP_FTYPE_RESOURCE) ;
1049   ap_hook_post_config(mod_proxy_html, NULL, NULL, APR_HOOK_MIDDLE) ;
1050   ap_hook_child_init(proxy_html_child_init, NULL, NULL, APR_HOOK_MIDDLE) ;
1051 }
1052 module AP_MODULE_DECLARE_DATA proxy_html_module = {
1053         STANDARD20_MODULE_STUFF,
1054         proxy_html_config,
1055         proxy_html_merge,
1056         NULL,
1057         NULL,
1058         proxy_html_cmds,
1059         proxy_html_hooks
1060 } ;
1061