From 356ace1a5f618d00f145b640acb989dd0197e8d6 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 26 Apr 2021 23:36:30 +0000 Subject: Use filter instead of handler This makes it work for HTML content that has already been processed, e.g., by PHP. This change was delightfully easy to make. --- README | 28 ++++++------- lib/Apache/Inject.pm | 32 +++++++-------- lib/Apache/Inject/Filter.pm | 93 ++++++++++++++++++++++++++++++++++++++++++++ lib/Apache/Inject/Handler.pm | 90 ------------------------------------------ 4 files changed, 123 insertions(+), 120 deletions(-) create mode 100644 lib/Apache/Inject/Filter.pm delete mode 100644 lib/Apache/Inject/Handler.pm diff --git a/README b/README index 1f396ca..4a663c0 100644 --- a/README +++ b/README @@ -99,23 +99,24 @@ SYNTAX OPERATION Behind the scenes, the Inject directive works as an alias for - PerlResponseHandler and PerlSetVar. For example, "Inject head.html + PerlOutputFilterHandler and PerlSetVar. For example, "Inject head.html foot.html" results in the following configuration being added: - PerlResponseHandler Apache::Inject::Handler + PerlOutputFilterHandler Apache::Inject::Filter PerlSetVar InjectHeader head.html PerlSetVar InjectFooter foot.html - This results in Apache::Inject::Handler being registered as a handler - for requests to the current directory or location. + This results in Apache::Inject::Filter being registered as an output + filter for requests to the current directory or location. - Apache::Inject::Handler accepts all requests to files where the content - type is "text/html". It reads the contents of the requested file, as - well as the contents of the "InjectHeader" and "InjectFooter" files, - concatenates them intelligently and prints their combined contents. + Apache::Inject::Filter accepts all requests where the content type is + "text/html". It receives the contents of the original page from Apache + and, in addition, reads the contents of the "InjectHeader" and + "InjectFooter" files. It then concatenates all of these intelligently + and forwards their combined contents. CAVEATS - Apache::Inject::Handler uses regular expressions to determine the proper + Apache::Inject::Filter uses regular expressions to determine the proper location of the injected header. It supports all valid HTML. However, it does not take into account that embedded CSS and JavaScript code can contain strings that look like valid opening and closing HTML tags. @@ -125,11 +126,10 @@ CAVEATS module; it is only the tests that require it. DIAGNOSTICS - Apache::Inject and Apache::Inject::Handler log all errors and warnings - to the Apache log file. Below is a list of all issued errors and - warnings. + Apache::Inject and Apache::Inject::Filter log all errors and warnings to + the Apache log file. Below is a list of all issued errors and warnings. - Note that whenever Apache::Inject::Handler issues an error or a warning, + Note that whenever Apache::Inject::Filter issues an error or a warning, this means that it also declines the request, letting Apache handle it as it would if the Inject directive were not used. @@ -152,7 +152,7 @@ DIAGNOSTICS exist. Warning: Declining request due to empty document root - This warning is issued if Apache::Inject::Handler for some reason + This warning is issued if Apache::Inject::Filter for some reason cannot retrieve the current document root from Apache. AUTHOR diff --git a/lib/Apache/Inject.pm b/lib/Apache/Inject.pm index 0670e69..bf50728 100644 --- a/lib/Apache/Inject.pm +++ b/lib/Apache/Inject.pm @@ -31,8 +31,7 @@ sub Inject { # Add relevant directives to current configuration $parms->add_config([ - 'SetHandler perl-script', - 'PerlResponseHandler Apache::Inject::Handler', + 'PerlOutputFilterHandler Apache::Inject::Filter', qq{PerlSetVar InjectHead "$head"}, qq{PerlSetVar InjectFoot "$foot"}, ]); @@ -164,25 +163,26 @@ to a hyphen. =head1 OPERATION Behind the scenes, the Inject directive works as an alias for -PerlResponseHandler and PerlSetVar. For example, C results in the following configuration being added: +PerlOutputFilterHandler and PerlSetVar. For example, C results in the following configuration being +added: - PerlResponseHandler Apache::Inject::Handler + PerlOutputFilterHandler Apache::Inject::Filter PerlSetVar InjectHeader head.html PerlSetVar InjectFooter foot.html -This results in Apache::Inject::Handler being registered as a handler -for requests to the current directory or location. +This results in Apache::Inject::Filter being registered as an output +filter for requests to the current directory or location. -Apache::Inject::Handler accepts all requests to files where the -content type is C. It reads the contents of the requested -file, as well as the contents of the C and C -files, concatenates them intelligently and prints their combined -contents. +Apache::Inject::Filter accepts all requests where the content type +is C. It receives the contents of the original page +from Apache and, in addition, reads the contents of the C +and C files. It then concatenates all of these +intelligently and forwards their combined contents. =head1 CAVEATS -Apache::Inject::Handler uses regular expressions to determine the +Apache::Inject::Filter uses regular expressions to determine the proper location of the injected header. It supports all valid HTML. However, it does not take into account that embedded CSS and JavaScript code can contain strings that look like valid opening @@ -194,11 +194,11 @@ without the module; it is only the tests that require it. =head1 DIAGNOSTICS -Apache::Inject and Apache::Inject::Handler log all errors and +Apache::Inject and Apache::Inject::Filter log all errors and warnings to the Apache log file. Below is a list of all issued errors and warnings. -Note that whenever Apache::Inject::Handler issues an error or a +Note that whenever Apache::Inject::Filter issues an error or a warning, this means that it also declines the request, letting Apache handle it as it would if the Inject directive were not used. @@ -227,7 +227,7 @@ exist. =item Warning: Declining request due to empty document root -This warning is issued if Apache::Inject::Handler for some reason +This warning is issued if Apache::Inject::Filter for some reason cannot retrieve the current document root from Apache. =back diff --git a/lib/Apache/Inject/Filter.pm b/lib/Apache/Inject/Filter.pm new file mode 100644 index 0000000..f87cdc1 --- /dev/null +++ b/lib/Apache/Inject/Filter.pm @@ -0,0 +1,93 @@ +package Apache::Inject::Filter; + +use 5.010000; +use strict; + +use mod_perl2; +use base qw/Apache2::Filter/; +use Apache2::Const qw/OK DECLINED/; +use Apache2::Log (); +use Apache2::RequestRec (); +use Apache2::RequestUtil (); + +my $doc = qr{ + \A + (? \s* + ( )? \s* + ( ]*> )? \s* + ( )? \s* + ( ]*> )? \s* + ( )? \s* + ( ]*> .*? \s* + | ( ]*> \s* + | ]*> \s* + | ]*> .*? \s* + | ]*> .*? \s* + | ]*> .*? \s* + | ]*> \s* + | \s* + )+ + )? + ( )? \s* + ( ]*> )? \s* + )? + (? .*? ) + (? \s* + ( )? \s* + )? + \z +}xmsi; + +sub handler : FilterRequestHandler { + my $f = shift; + + return DECLINED if not $f->r->content_type; + return DECLINED if not $f->r->content_type =~ m{^text/html($|;.*)}; + + if (not $f->r->document_root) { + $f->r->warn('Inject: Declining due to empty document root'); + return DECLINED; + } + + my ($buf, $content); + $content .= $buf while $f->read($buf); + return DECLINED if not $content =~ /$doc/; + + $f->print($+{head}) if $+{head}; + inject($f, "InjectHead"); + $f->print($+{body}) if $+{body}; + inject($f, "InjectFoot"); + $f->print($+{rest}) if $+{rest}; + + return OK; +} + +sub inject { + my ($f, $var) = @_; + + # Retrieve value implicitly set by Inject directive + return if not (my $val = $f->r->dir_config($var)); + return if $val eq '-'; # special value signifying absence of argument + + # Validate path + if ($val =~ m{^/}) { + $f->r->log_error("Inject: $var should not begin with slash, as it is already always relative to document root"); + } + if ($val =~ m{^../|/../|/..$}) { + $f->r->log_error("Inject: $var cannot extend past document root"); + return; + } + + # note: document root has been confirmed not to be empty + my $root = $f->r->document_root; + + # Read contents of specified file + open my $fh, '<', "$root/$val" or do { + $f->r->log_error("Inject: $var $root/$val does not exist"); + return; + }; + $f->print($_) for <$fh>; + close $fh; +} + +1; diff --git a/lib/Apache/Inject/Handler.pm b/lib/Apache/Inject/Handler.pm deleted file mode 100644 index ae81fbb..0000000 --- a/lib/Apache/Inject/Handler.pm +++ /dev/null @@ -1,90 +0,0 @@ -package Apache::Inject::Handler; - -use 5.010000; -use strict; - -use mod_perl2; -use Apache2::Const qw/OK DECLINED/; -use Apache2::Log (); -use Apache2::RequestRec (); -use Apache2::RequestUtil (); - -my $doc = qr{ - \A - (? \s* - ( )? \s* - ( ]*> )? \s* - ( )? \s* - ( ]*> )? \s* - ( )? \s* - ( ]*> .*? \s* - | ( ]*> \s* - | ]*> \s* - | ]*> .*? \s* - | ]*> .*? \s* - | ]*> .*? \s* - | ]*> \s* - | \s* - )+ - )? - ( )? \s* - ( ]*> )? \s* - )? - (? .*? ) - (? \s* - ( )? \s* - )? - \z -}xmsi; - -sub handler { - my $r = shift; - - return DECLINED if not $r->content_type eq 'text/html'; - - my $content = ${$r->slurp_filename}; - return DECLINED if not $content =~ /$doc/; - - if (not $r->document_root) { - $r->warn('Inject: Declining request due to empty document root'); - return DECLINED; - } - - print $+{head} if $+{head}; - inject($r, "InjectHead"); - print $+{body} if $+{body}; - inject($r, "InjectFoot"); - print $+{rest} if $+{rest}; - - return OK; -} - -sub inject { - my ($r, $var) = @_; - - # Retrieve value implicitly set by Inject directive - return if not (my $val = $r->dir_config($var)); - return if $val eq '-'; # special value signifying absence of argument - - # Validate path - if ($val =~ m{^/}) { - $r->log_error("Inject: $var should not begin with slash, as it is already always relative to document root"); - } - if ($val =~ m{^../|/../|/..$}) { - $r->log_error("Inject: $var cannot extend past document root"); - return; - } - - # note: document root has been confirmed not to be empty - my $root = $r->document_root; - - # Read contents of specified file - open my $fh, '<', "$root/$val" or do { - $r->log_error("Inject: $var $root/$val does not exist"); - return; - }; - print for <$fh>; - close $fh; -} - -1; -- cgit v1.2.3