File Coverage

blib/lib/Apache/ProxyStuff.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Apache::ProxyStuff;
2              
3 1     1   1179 use strict;
  1         3  
  1         50  
4 1     1   5 use vars qw(@ISA $VERSION);
  1         2  
  1         112  
5 1     1   5875 use Apache::Constants qw(:common);
  0            
  0            
6             use Apache::Log;
7             use Apache::Table;
8             use HTML::TokeParser;
9             use LWP::UserAgent;
10             use Data::Dumper;
11              
12             @ISA = qw(LWP::UserAgent);
13             $VERSION = '0.10';
14              
15             my $UA = __PACKAGE__->new;
16             $UA->agent(join "/", __PACKAGE__, $VERSION);
17              
18             # Override Methods
19             sub redirect_ok {return 0}
20              
21             # Helper Subs
22             sub set_headers {
23             my ($req, %headers) = @_;
24             foreach my $header (keys %headers) {
25             next if $header eq 'Connection'; # Don't want to pass Keep-Alive
26             $req->push_header($header => $headers{$header});
27             } # End foreach
28              
29             # Set REMOTE_ADDR, REMOTE_HOST, REMOTE_USER
30             $req->push_header('REMOTE_ADDR' => $ENV{'REMOTE_ADDR'});
31             $req->push_header('REMOTE_HOST' => $ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'});
32             $req->push_header('REMOTE_USER' => $ENV{'REMOTE_USER'});
33             return $req;
34             } # End set_headers
35              
36             sub open_head {
37             my ($token, $r, $meta_description, $meta_content) = @_;
38              
39             # Print tag
40             print qq($token->[-1]\n);
41              
42             # Print meta tags
43             print $meta_description;
44             print $meta_content;
45             } # End open_head()
46              
47             sub open_body {
48             my ($token, $r, $header, $body_attributes) = @_;
49            
50             # Print body tag
51             print q(
52            
53             # Replace existing body attributes with new ones if necessary
54             if ($body_attributes) {
55             foreach my $pair (split /\s+/, $body_attributes) {
56             my ($attr, $value) = split /=/, $pair;
57             $token->[2]->{lc $attr} = $value; # Keys are lowercase
58             } # End foreach
59             } # End if
60              
61             # Print attributes
62             my $atts = join(' ', map({"$_=$token->[2]->{$_}"} keys %{$token->[2]}));
63             print qq( $atts) if $atts;
64            
65             # Close body tag
66             print qq(>);
67              
68             # Send the header
69             print $header;
70             } # End open_body()
71              
72             sub close_body {
73             my ($token, $r, $footer) = @_;
74            
75             # Send the footer
76             print $footer;
77             print $token->[-1];
78             } # End close_body()
79              
80             sub a_href {
81             my ($token, $r, $add_host2href) = @_;
82              
83             # Open tag
84             print qq(
85              
86             # Modify href
87             if ($token->[2]->{'href'} =~ /^\//) { # Modify if absolute URI
88             $token->[2]->{'href'} = qq(/$add_host2href) . $token->[2]->{'href'};
89             } # End unless
90            
91             # Print attributes
92             my $atts = join(' ', map({"$_=$token->[2]->{$_}"} keys %{$token->[2]}));
93             print qq( $atts) if $atts;
94            
95             # Close tag
96             print qq(>);
97             } # End a_href()
98              
99             sub img_src {
100             my ($token, $r, $add_host2img_src) = @_;
101            
102             # Open tag
103             print qq(
104            
105             # Modify src
106             if ($token->[2]->{'src'} =~ /^\//) { # Modify is absolute URI
107             $token->[2]->{'src'} = qq(/$add_host2img_src) . $token->[2]->{'src'};
108             } # End unless
109            
110             # Print attributes
111             my $atts = join(' ', map({"$_=$token->[2]->{$_}"} keys %{$token->[2]}));
112             print qq( $atts) if $atts;
113            
114             # Close tag
115             print qq(>);
116             } # End a_href()
117              
118             sub form_action {
119             my ($token, $r, $add_host2form_action) = @_;
120              
121             # Open tag
122             print qq(
123            
124             # Modify action
125             if ($token->[2]->{'action'} =~ /^\//) { # Modify is absolute URI
126             $token->[2]->{'action'} = qq(/$add_host2form_action) . $token->[2]->{'action'};
127             } # End unless
128            
129             # Print attributes
130             my $atts = join(' ', map({"$_=$token->[2]->{$_}"} keys %{$token->[2]}));
131             print qq( $atts) if $atts;
132            
133             # Close tag
134             print qq(>);
135             } # End a_href()
136              
137             sub process_text {
138            
139             my ($content, $r, $header, $footer, $meta_description, $meta_content, $body_attributes,
140             $add_host2href, $add_host2img_src, $add_host2form_action) = @_;
141              
142             # Parse the document
143             my $parser = new HTML::TokeParser($content);
144              
145             # Pre-process the beginning of document so we can appropriately handled bad documents
146             # that are missing the tag
147             my ($found_html, @tokens_up_to_html, $found_head, @tokens_up_to_head, $found_body, @tokens,
148             $found_close_body, @tokens_before_close_html, $found_close_html);
149             while (my $token = $parser->get_token) {
150             # If it's save the previous tokens and this one in a seperate array
151             if ($token->[0] eq 'S' and $token->[1] eq 'html') {
152             $found_html++;
153             push @tokens_up_to_html, @tokens, $token;
154             $r->log->debug("HTML Tokens: ", Dumper(@tokens_up_to_html), qq(\n\n));
155             @tokens = ();
156             } # End if
157             # If it's save the previous tokens and this one in a seperate array
158             elsif ($token->[0] eq 'E' and $token->[1] eq 'head') {
159             $found_head++;
160             push @tokens_up_to_head, @tokens, $token;
161             $r->log->debug(" Tokens: ", Dumper(@tokens_up_to_head), qq(\n\n));
162             @tokens = ();
163             } # End elsif
164             # If it's add this one to the stack and set a flag
165             elsif ($token->[0] eq 'S' and $token->[1] eq 'body') {
166             $found_body++;
167             push @tokens, $token;
168             } # End elsif
169             # If it's add this one to the stack and set a flag
170             elsif ($token->[0] eq 'E' and $token->[1] eq 'body') {
171             $found_close_body++;
172             push @tokens, $token;
173             } # End elsif
174             # If it's save the previous tokens in a seperate array
175             elsif ($token->[0] eq 'E' and $token->[1] eq 'html') {
176             $found_close_html++;
177             push @tokens_before_close_html, @tokens;
178             @tokens = $token;
179             } # End elsif
180             # Otherwise just save up the tokens
181             else {push @tokens, $token}
182             } # End while
183              
184             # Build our body tags in case we need them
185             my $body_tag = ['S', 'body', {}, [], ''];
186             my $close_body_tag = ['E', 'body', {}, [], ''];
187              
188             # Rebuild the master array of tokens
189             # If we found just make one big of array of the tokens we saved
190             my @all_tokens;
191             if ($found_body) {@all_tokens = (@tokens_up_to_html, @tokens_up_to_head)}
192              
193             # If we found but no add after the
194             elsif ($found_head) {@all_tokens = (@tokens_up_to_html, @tokens_up_to_head, $body_tag)}
195              
196             # If we found but no and no add after
197             elsif ($found_html) {@all_tokens = (@tokens_up_to_html, $body_tag)}
198              
199             # We didn't find , and so add to the beginning
200             else {@all_tokens = ($body_tag)}
201            
202             # If we found just add the rest onto the end
203             if ($found_close_body) {push @all_tokens, @tokens_before_close_html, @tokens}
204              
205             # If we found but no insert after
206             elsif ($found_close_html) {push @all_tokens, @tokens_before_close_html, $close_body_tag, @tokens}
207            
208             # We didn't find or add to the end of the document
209             else {push @all_tokens, @tokens, $close_body_tag}
210              
211             # Put them back on the parser
212             $parser->unget_token(@all_tokens);
213            
214             # Now actually process the document
215             my ($saw_header, $saw_footer); # We need these for broken docs that have multiple tags
216             while (my $token = $parser->get_token) {
217              
218             # Handle
219             if ($token->[0] eq 'S' and $token->[1] eq 'head') {
220             open_head($token, $r, $meta_description, $meta_content) if $meta_description or $meta_content;
221             } # End if
222              
223             # Handle
224             elsif ($token->[0] eq 'S' and $token->[1] eq 'body' and not $saw_header) {
225             open_body($token, $r, $header, $body_attributes);
226             $saw_header++;
227             } # End if
228            
229             # Handle
230             elsif ($token->[0] eq 'E' and $token->[1] eq 'body' and not $saw_footer) {
231             close_body($token, $r, $footer);
232             $saw_footer++;
233             } # End elsif
234            
235             # Handle
236             elsif ($add_host2href and $token->[0] eq 'S' and $token->[1] eq 'a' and
237             $token->[2]->{'href'}) {a_href($token, $r, $add_host2href)}
238              
239             # Handle
240             elsif ($add_host2img_src and $token->[0] eq 'S' and $token->[1] eq 'img' and
241             $token->[2]->{'src'}) {img_src($token, $r, $add_host2img_src)}
242            
243             # Handle
244             elsif ($add_host2form_action and $token->[0] eq 'S' and
245             $token->[1] eq 'form' and $token->[2]->{'action'}) {form_action($token, $r,
246             $add_host2form_action)}
247            
248             # Handle comments because TokeParser doesn't save the original text for them
249             elsif ($token->[0] eq 'C') {print qq()}
250            
251             # Ditto for declarations
252             elsif ($token->[0] eq 'D') {print qq([-1]>)}
253            
254             # Handle text, I think it's different in newer versions of HTML::TokeParser
255             elsif ($token->[0] eq 'T') {print qq($token->[1])}
256              
257             # Handle everything else
258             else { print $token->[-1]}
259            
260             } # End while
261             } # End process_text()
262              
263              
264             # Handler
265             sub handler {
266              
267             my $r = shift;
268              
269             # Get configuration
270             my $header_file = $r->dir_config('HeaderFile');
271             my $footer_file = $r->dir_config('FooterFile');
272             my $proxy_prefix = $r->dir_config('ProxyPrefix');
273             my $meta_description = qq( 274             qq(">\n);
275             my $meta_content = qq(\n);
276             my $body_attributes = $r->dir_config('BodyAttributes');
277             my $strip_host = $r->dir_config('StripHost');
278             my $add_host2href = $r->dir_config('AddHost2AHref');
279             my $add_host2img_src = $r->dir_config('AddHost2ImgSrc');
280             my $add_host2form_action = $r->dir_config('AddHost2FormAction');
281              
282             # Mangle the url for the file as needed
283             my ($null, $base, $uri);
284             if ($strip_host) {($null, $base, $uri) = split /\//, $r->uri, 3}
285             else {$uri = $r->uri}
286             $uri =~ s/^\///; # Remove leading slashes
287             my $file_uri = join '/', $proxy_prefix, $uri;
288             $file_uri .= q(?) . $r->args if $r->args;
289             $r->log->debug("URI: $file_uri");
290              
291             # Build the request
292             my $req = new HTTP::Request($r->method => $file_uri);
293              
294             # Set headers
295             $req = set_headers($req, $r->headers_in);
296              
297             # Copy POST data, if any
298             if ($r->method eq 'POST') {
299             my $len = $r->header_in('Content-length');
300             my $buf;
301             $r->read($buf, $len);
302             $req->content($buf);
303             } # End if
304            
305             # Run the request
306             my $res = $UA->request($req);
307              
308             if ($res->is_redirect) {
309             my $location = $res->header('Location');
310             my ($host) = ($location =~ m!^([^/]+//[^/]+)/!);
311             if ($host eq $proxy_prefix) {
312             my $hostname = $r->server->server_hostname;
313             $location =~ s!//([^/]+)/!//$hostname/!;
314             $res->header('Location' => $location);
315             } # End if
316             } # End if
317              
318             # Handle all other headers
319             # $res->scan(sub {$r->header_out(@_);});
320             $res->scan(sub {$r->headers_out->add(@_);}); # Use this one to handle multiple headers of same name
321              
322             # Handle special headers
323             $r->content_type($res->header('Content-type'));
324             $r->status($res->code);
325             $r->status_line($res->status_line);
326              
327             # HEAD request?
328             if ($r->header_only) {
329             $r->send_http_header;
330             return OK;
331             } # End if
332              
333             # Get the content
334             my $content = $res->content_ref;
335              
336             # If it's text
337             if ($r->content_type =~ /^text/) {
338              
339             # Get the header and footer
340             my $header_req = new HTTP::Request('GET' => $header_file);
341             my $footer_req = new HTTP::Request('GET' => $footer_file);
342             $header_req = set_headers($header_req, $r->headers_in);
343             $footer_req = set_headers($footer_req, $r->headers_in);
344             $header_req->push_header('REAL_URI' => $file_uri); # Somebody might need the real page
345             $footer_req->push_header('REAL_URI' => $file_uri); # Ditto
346             $header_req->push_header('ORIG_URI' => $r->uri); # Somebody might need the real page
347             $footer_req->push_header('ORIG_URI' => $r->uri); # Ditto
348             my $header_res = $UA->request($header_req);
349             my $footer_res = $UA->request($footer_req);
350              
351             # Adjust the content length to include the lenght of the header and footer
352             my $length = length($header_res->content) + length($footer_res->content) + length($res->content) +
353             length($body_attributes) + length($meta_description) + length($meta_content);
354             $r->header_out('Content-length' => $length);
355             $r->send_http_header;
356             process_text($content, $r, $header_res->content, $footer_res->content, $meta_description,
357             $meta_content, $body_attributes, $add_host2href, $add_host2img_src,
358             $add_host2form_action);
359             } # End if
360            
361             else {$r->send_http_header; print $$content}
362            
363             return OK;
364              
365             } # End handler()
366              
367             1;
368              
369             __END__