aboutsummaryrefslogtreecommitdiff
path: root/lib/Apache/Inject.pm
blob: bf50728f95e5114e0055c9a6d6aa0971cf9646dd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
package Apache::Inject;

use 5.010000;
use strict;

our $VERSION = '0.01';

use mod_perl2;
use Apache2::CmdParms ();
use Apache2::Const qw/OR_LIMIT OR_AUTHCFG TAKE12/;
use Apache2::Log ();
use Apache2::Module ();

my @directives = ({
	name => 'Inject',
	func => __PACKAGE__.'::Inject',
	req_override => OR_LIMIT|OR_AUTHCFG,
	args_how => TAKE12,
	errmsg => 'Inject HeadFile[!] FootFile[!]',
});
Apache2::Module::add(__PACKAGE__, \@directives);

sub Inject {
	my ($self, $parms, @args) = @_;

	# Construct directives for passing arguments to handler
	my $head = $args[0];
	my $foot = $args[1] || '-'; # single hyphen signifies absence of argument
	$head =~ s/\\/\\\\/; $head =~ s/"/\\"/;
	$foot =~ s/\\/\\\\/; $foot =~ s/"/\\"/;

	# Add relevant directives to current configuration
	$parms->add_config([
		'PerlOutputFilterHandler Apache::Inject::Filter',
		qq{PerlSetVar InjectHead "$head"},
		qq{PerlSetVar InjectFoot "$foot"},
	]);
}

1;
__END__
=encoding latin1

=head1 NAME

Apache::Inject - Apache directive for injecting HTML headers and footers

=head1 SYNOPSIS

  LoadModule perl_module libexec/apache24/mod_perl.so
  PerlLoadModule Apache::Inject
  DocumentRoot /usr/local/www/apache24/data

  <Directory /usr/local/www/apache24/data>
      # Inject both header and footer on all pages on the server
      Inject head.html foot.html
  </Directory>

  <Location /blog>
      # Inject only header on pages under /blog
      Inject head.html
  </Location>

  <Files index.html>
      # Inject only footer on pages named index.html
      Inject - foot.html
  </Files>

=head1 DESCRIPTION

Apache::Inject is a mod_perl module that adds an Apache directive
called Inject.

The Inject directive takes one or two arguments, which correspond
to the file names of two HTML files in the document root.  The
contents of these files are then inserted into any requested HTML
file.  The first file (the header) is inserted at the top of the
body of the requested HTML file, while the second, optional file
(the footer) is inserted at the bottom of the body.

The directive is smart enough to place the header and footer in the
proper places.  The contents of the header file is inserted after
any elements belonging to E<lt>headE<gt> and before any elements
belonging to E<lt>bodyE<gt> (regardless of whether any explicit
E<lt>headE<gt> or E<lt>bodyE<gt> tag is present in the source of
the requested HTML page).  Likewise, the contents of the second
file is placed before any potential final E<lt>/htmlE<gt>.

The Inject directive serves a much more specific purpose than
server-side includes.  It is designed for injecting headers and
footers that belong in the E<lt>bodyE<gt> element, such as headings,
menu bars and copyright notices.  While you can technically include
E<lt>headE<gt> elements in your header file, Apache::Inject will
place them in the body of the HTML page and not in the head.

The main benefit over server-side includes is that the header and
footer is specified in the server configuration instead of the HTML
files themselves.  Thus, it is useful for adding headers and footers
to a large number of pre-existing static HTML pages.  Furthermore,
this means that the headers and footers on all pages can be changed
at once by a single change in the server configuration.

Please note:

=over

=item *
The Inject directive is valid only inside directory sections, such
as E<lt>DirectoryE<gt>, E<lt>LocationE<gt> and E<lt>FilesMatchE<gt>
blocks.  It is valid in .htaccess files if AllowOverride
Limit/AuthConfig/All is enabled.

=item *
The file paths given to Inject are relative to the B<document root>
of the current server or virtual server -- B<not> the directory to
which the current directory section or .htaccess file applies.  They
should be specified without a leading slash.

=back

=head1 INSTALLATION

To install this module type the following:

  perl Makefile.PL
  make
  make test
  make install

Note that all steps in this process require mod_perl2 to be installed.
I recommend installing mod_perl2 via your operating system's package
manager or ports collection before installing Apache::Inject.

Note further that, because they depend on Apache, the tests require
an unprivileged user and will be skipped if they are run as root.
This is relevant if you install Apache::Inject via App::Cpan, which
normally runs as root.

=head1 SYNTAX

The Inject directive takes one or two arguments:

  Inject HEADER_FILE [FOOTER_FILE]

Each argument can consist of one of two things:

=over

=item 1.
the path to a file relative to the document root, or

=item 2.
a single hyphen (C<->), signifying the absence of an argument.

=back

Passing a hyphen as the first argument disables the header, and
passing a hyphen as the second argument disables the footer.

If you leave out the second argument, then it is implicitly equivalent
to a hyphen.

=head1 OPERATION

Behind the scenes, the Inject directive works as an alias for
PerlOutputFilterHandler and PerlSetVar.  For example, C<Inject
head.html foot.html> results in the following configuration being
added:

  PerlOutputFilterHandler Apache::Inject::Filter
  PerlSetVar InjectHeader head.html
  PerlSetVar InjectFooter foot.html

This results in Apache::Inject::Filter being registered as an output
filter for requests to the current directory or location.

Apache::Inject::Filter accepts all requests where the content type
is C<text/html>.  It receives the contents of the original page
from Apache and, in addition, reads the contents of the C<InjectHeader>
and C<InjectFooter> files.  It then concatenates all of these
intelligently and forwards their combined contents.

=head1 CAVEATS

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.

On FreeBSD, you may need to enable the accf_http kernel module in
order for the tests to work.  Note that Apache::Inject works fine
without the module; it is only the tests that require it.

=head1 DIAGNOSTICS

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::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.

=over

=item Error: InjectHead/InjectFoot should not begin with slash, as
it is already always relative to document root

The paths given to Inject are always relative to the document root,
even if the Inject directive is located within a directory section
that applies to another path.

Beginning any of the paths with a slash implies that there would
be some difference in behavior compared to omitting the slash, which
is false.

=item Error: InjectHead/InjectFoot cannot extend past document root

This error is issued if any of the paths given to Inject tries to
go above the document root by using C<../>.

=item Error: InjectHead/InjectFoot I<path/to/file> does not exist

This error is issued if any of the paths given to Inject doesn't
exist.

=item Warning: Declining request due to empty document root

This warning is issued if Apache::Inject::Filter for some reason
cannot retrieve the current document root from Apache.

=back

=head1 AUTHOR

John Ankarström, E<lt>john [at] ankarstrom.seE<gt>

=head1 SEE ALSO

mod_perl2:
L<https://perl.apache.org/>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2021 by John Ankarström

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.32.1
or, at your option, any later version of Perl 5 you may have
available.

=cut