line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CGI::Buffer; |
2
|
|
|
|
|
|
|
|
3
|
6
|
|
|
6
|
|
2013493
|
use 5.14.0; # For IO::Compress::Brotli |
|
6
|
|
|
|
|
58
|
|
4
|
6
|
|
|
6
|
|
34
|
use strict; |
|
6
|
|
|
|
|
26
|
|
|
6
|
|
|
|
|
139
|
|
5
|
6
|
|
|
6
|
|
28
|
use warnings; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
228
|
|
6
|
|
|
|
|
|
|
|
7
|
6
|
|
|
6
|
|
37
|
use Digest::MD5; |
|
6
|
|
|
|
|
11
|
|
|
6
|
|
|
|
|
239
|
|
8
|
6
|
|
|
6
|
|
2858
|
use IO::String; |
|
6
|
|
|
|
|
20213
|
|
|
6
|
|
|
|
|
249
|
|
9
|
6
|
|
|
6
|
|
3635
|
use CGI::Info; |
|
6
|
|
|
|
|
448030
|
|
|
6
|
|
|
|
|
396
|
|
10
|
6
|
|
|
6
|
|
66
|
use Carp; |
|
6
|
|
|
|
|
12
|
|
|
6
|
|
|
|
|
418
|
|
11
|
6
|
|
|
6
|
|
3174
|
use HTTP::Date; |
|
6
|
|
|
|
|
23138
|
|
|
6
|
|
|
|
|
362
|
|
12
|
6
|
|
|
6
|
|
47
|
use Text::Diff; # For debugging |
|
6
|
|
|
|
|
21
|
|
|
6
|
|
|
|
|
425
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 NAME |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
CGI::Buffer - Verify, Cache and Optimise CGI Output |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 VERSION |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
Version 0.83 |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
=cut |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
our $VERSION = '0.83'; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 SYNOPSIS |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
CGI::Buffer verifies the HTML that you produce by passing it through |
29
|
|
|
|
|
|
|
C<HTML::Lint>. |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
CGI::Buffer optimises CGI programs by reducing, filtering and compressing |
32
|
|
|
|
|
|
|
output to speed up the transmission and by nearly seamlessly making use of |
33
|
|
|
|
|
|
|
client and server caches. |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
To make use of client caches, that is to say to reduce needless calls |
36
|
|
|
|
|
|
|
to your server asking for the same data, all you need to do is to |
37
|
|
|
|
|
|
|
include the package, and it does the rest. |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
use CGI::Buffer; |
40
|
|
|
|
|
|
|
# ... |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
To also make use of server caches, that is to say to save regenerating |
43
|
|
|
|
|
|
|
output when different clients ask you for the same data, you will need |
44
|
|
|
|
|
|
|
to create a cache. |
45
|
|
|
|
|
|
|
But that's simple: |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
use CHI; |
48
|
|
|
|
|
|
|
use CGI::Buffer; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# Put this at the top before you output anything |
51
|
|
|
|
|
|
|
CGI::Buffer::init( |
52
|
|
|
|
|
|
|
cache => CHI->new(driver => 'File') |
53
|
|
|
|
|
|
|
); |
54
|
|
|
|
|
|
|
if(CGI::Buffer::is_cached()) { |
55
|
|
|
|
|
|
|
# Nothing has changed - use the version in the cache |
56
|
|
|
|
|
|
|
exit; |
57
|
|
|
|
|
|
|
} |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# ... |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
To temporarily prevent the use of server-side caches, for example whilst |
62
|
|
|
|
|
|
|
debugging before publishing a code change, set the NO_CACHE environment variable |
63
|
|
|
|
|
|
|
to any non-zero value. |
64
|
|
|
|
|
|
|
If you get errors about Wide characters in print it means that you've |
65
|
|
|
|
|
|
|
forgotten to emit pure HTML on non-ASCII characters. |
66
|
|
|
|
|
|
|
See L<HTML::Entities>. |
67
|
|
|
|
|
|
|
As a hack work around you could also remove accents and the like by using |
68
|
|
|
|
|
|
|
L<Text::Unidecode>, |
69
|
|
|
|
|
|
|
which works well but isn't really what you want. |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 SUBROUTINES/METHODS |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=cut |
74
|
|
|
|
|
|
|
|
75
|
6
|
|
|
6
|
|
44
|
use constant MIN_GZIP_LEN => 32; |
|
6
|
|
|
|
|
20
|
|
|
6
|
|
|
|
|
1396
|
|
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
our $generate_etag = 1; |
78
|
|
|
|
|
|
|
our $generate_304 = 1; |
79
|
|
|
|
|
|
|
our $generate_last_modified = 1; |
80
|
|
|
|
|
|
|
our $compress_content = 1; |
81
|
|
|
|
|
|
|
our $optimise_content = 0; |
82
|
|
|
|
|
|
|
our $lint_content = 0; |
83
|
|
|
|
|
|
|
our $cache; |
84
|
|
|
|
|
|
|
our $cache_age; |
85
|
|
|
|
|
|
|
our $cache_key; |
86
|
|
|
|
|
|
|
our $info; |
87
|
|
|
|
|
|
|
our $logger; |
88
|
|
|
|
|
|
|
our $lingua; |
89
|
|
|
|
|
|
|
our $status; |
90
|
|
|
|
|
|
|
our $script_mtime; |
91
|
|
|
|
|
|
|
our $cobject; |
92
|
|
|
|
|
|
|
our($x_cache, $buf, $headers, $header, $body, @content_type, $etag, |
93
|
|
|
|
|
|
|
$send_body, @o, $encode_loaded); |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
BEGIN { |
96
|
|
|
|
|
|
|
# use Exporter(); |
97
|
|
|
|
|
|
|
|
98
|
6
|
|
|
6
|
|
73
|
$CGI::Buffer::buf = IO::String->new(); |
99
|
6
|
|
|
|
|
405
|
$CGI::Buffer::old_buf = select($CGI::Buffer::buf); |
100
|
|
|
|
|
|
|
|
101
|
6
|
50
|
33
|
|
|
58
|
if((!defined($ENV{'SERVER_PROTOCOL'})) || |
102
|
|
|
|
|
|
|
($ENV{'SERVER_PROTOCOL'} eq 'HTTP/1.0')) { |
103
|
6
|
|
|
|
|
44390
|
$generate_etag = 0; |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
END { |
108
|
6
|
50
|
33
|
6
|
|
3476261
|
if(defined($^V) && ($^V ge 'v5.14.0')) { |
109
|
6
|
50
|
|
|
|
60
|
return if ${^GLOBAL_PHASE} eq 'DESTRUCT'; # >= 5.14.0 only |
110
|
|
|
|
|
|
|
} |
111
|
|
|
|
|
|
|
|
112
|
6
|
50
|
|
|
|
80
|
if($logger) { |
113
|
0
|
0
|
|
|
|
0
|
if($ENV{'HTTP_IF_NONE_MATCH'}) { |
114
|
0
|
|
|
|
|
0
|
$logger->debug("HTTP_IF_NONE_MATCH: $ENV{HTTP_IF_NONE_MATCH}"); |
115
|
|
|
|
|
|
|
} |
116
|
0
|
0
|
|
|
|
0
|
if($ENV{'HTTP_IF_MODIFIED_SINCE'}) { |
117
|
0
|
|
|
|
|
0
|
$logger->debug("HTTP_IF_MODIFIED_SINCE: $ENV{HTTP_IF_MODIFIED_SINCE}"); |
118
|
|
|
|
|
|
|
} |
119
|
0
|
|
|
|
|
0
|
$logger->debug("Generate_etag = $generate_etag, ", |
120
|
|
|
|
|
|
|
"Generate_304 = $generate_304, ", |
121
|
|
|
|
|
|
|
"Generate_last_modified = $generate_last_modified"); |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# This will cause everything to get flushed and prevent |
124
|
|
|
|
|
|
|
# outputs to the logger. We need to do that now since |
125
|
|
|
|
|
|
|
# if we leave it to Perl to delete later we may get |
126
|
|
|
|
|
|
|
# a message that Log4perl::init() hasn't been called |
127
|
0
|
|
|
|
|
0
|
$logger = undef; |
128
|
|
|
|
|
|
|
} |
129
|
6
|
|
|
|
|
54
|
select($CGI::Buffer::old_buf); |
130
|
6
|
|
|
|
|
80
|
my $pos = $CGI::Buffer::buf->getpos; |
131
|
6
|
|
|
|
|
177
|
$CGI::Buffer::buf->setpos(0); |
132
|
6
|
|
|
|
|
186
|
read($CGI::Buffer::buf, $buf, $pos); |
133
|
6
|
|
|
|
|
347
|
($headers, $body) = split /\r?\n\r?\n/, $buf, 2; |
134
|
|
|
|
|
|
|
|
135
|
6
|
50
|
33
|
|
|
81
|
unless($headers || is_cached()) { |
136
|
6
|
50
|
|
|
|
30
|
if($logger) { |
137
|
0
|
|
|
|
|
0
|
$logger->debug('There was no output'); |
138
|
|
|
|
|
|
|
} |
139
|
6
|
|
|
|
|
122
|
return; |
140
|
|
|
|
|
|
|
} |
141
|
0
|
0
|
0
|
|
|
0
|
if($ENV{'REQUEST_METHOD'} && ($ENV{'REQUEST_METHOD'} eq 'HEAD')) { |
142
|
0
|
|
|
|
|
0
|
$send_body = 0; |
143
|
|
|
|
|
|
|
} else { |
144
|
0
|
|
|
|
|
0
|
$send_body = 1; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
0
|
0
|
|
|
|
0
|
if($headers) { |
148
|
0
|
|
|
|
|
0
|
_set_content_type($headers); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
0
|
0
|
0
|
|
|
0
|
if(defined($body) && ($body eq '')) { |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
152
|
|
|
|
|
|
|
# E.g. if header of Location is given with no body, for |
153
|
|
|
|
|
|
|
# redirection |
154
|
0
|
|
|
|
|
0
|
$body = undef; |
155
|
0
|
0
|
|
|
|
0
|
if($cache) { |
156
|
|
|
|
|
|
|
# Don't try to retrieve it below from the cache |
157
|
0
|
|
|
|
|
0
|
$send_body = 0; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
} elsif(defined($content_type[0]) && (lc($content_type[0]) eq 'text') && (lc($content_type[1]) =~ /^html/) && defined($body)) { |
160
|
0
|
0
|
|
|
|
0
|
if($optimise_content) { |
161
|
|
|
|
|
|
|
# require HTML::Clean; |
162
|
0
|
|
|
|
|
0
|
require HTML::Packer; # Overkill using HTML::Clean and HTML::Packer... |
163
|
|
|
|
|
|
|
|
164
|
0
|
0
|
|
|
|
0
|
if($logger) { |
165
|
0
|
|
|
|
|
0
|
$logger->trace('Packer'); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
0
|
|
|
|
|
0
|
my $oldlength = length($body); |
169
|
0
|
|
|
|
|
0
|
my $newlength; |
170
|
|
|
|
|
|
|
|
171
|
0
|
0
|
|
|
|
0
|
if($optimise_content == 1) { |
172
|
0
|
|
|
|
|
0
|
_optimise_content(); |
173
|
|
|
|
|
|
|
} else { |
174
|
0
|
|
|
|
|
0
|
while(1) { |
175
|
0
|
|
|
|
|
0
|
_optimise_content(); |
176
|
0
|
|
|
|
|
0
|
$newlength = length($body); |
177
|
0
|
0
|
|
|
|
0
|
last if ($newlength >= $oldlength); |
178
|
0
|
|
|
|
|
0
|
$oldlength = $newlength; |
179
|
|
|
|
|
|
|
} |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# If we're on http://www.example.com and have a link |
183
|
|
|
|
|
|
|
# to http://www.example.com/foo/bar.htm, change the |
184
|
|
|
|
|
|
|
# link to /foo/bar.htm - there's no need to include |
185
|
|
|
|
|
|
|
# the site name in the link |
186
|
0
|
0
|
|
|
|
0
|
unless(defined($info)) { |
187
|
0
|
0
|
|
|
|
0
|
if($cache) { |
188
|
0
|
|
|
|
|
0
|
$info = CGI::Info->new({ cache => $cache }); |
189
|
|
|
|
|
|
|
} else { |
190
|
0
|
|
|
|
|
0
|
$info = CGI::Info->new(); |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
0
|
|
|
|
|
0
|
my $href = $info->host_name(); |
195
|
0
|
|
|
|
|
0
|
my $protocol = $info->protocol(); |
196
|
|
|
|
|
|
|
|
197
|
0
|
0
|
|
|
|
0
|
unless($protocol) { |
198
|
0
|
|
|
|
|
0
|
$protocol = 'http'; |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
0
|
$body =~ s/<a\s+?href="$protocol:\/\/$href"/<a href="\/"/gim; |
202
|
0
|
|
|
|
|
0
|
$body =~ s/<a\s+?href="$protocol:\/\/$href/<a href="/gim; |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
# TODO use URI->path_segments to change links in |
205
|
|
|
|
|
|
|
# /aa/bb/cc/dd.htm which point to /aa/bb/ff.htm to |
206
|
|
|
|
|
|
|
# ../ff.htm |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
# TODO: <img border=0 src=...> |
209
|
0
|
|
|
|
|
0
|
$body =~ s/<img\s+?src="$protocol:\/\/$href"/<img src="\/"/gim; |
210
|
0
|
|
|
|
|
0
|
$body =~ s/<img\s+?src="$protocol:\/\/$href/<img src="/gim; |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# Don't use HTML::Clean because of RT402 |
213
|
|
|
|
|
|
|
# my $h = new HTML::Clean(\$body); |
214
|
|
|
|
|
|
|
# # $h->compat(); |
215
|
|
|
|
|
|
|
# $h->strip(); |
216
|
|
|
|
|
|
|
# my $ref = $h->data(); |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
# Don't always do javascript 'best' since it's confused |
219
|
|
|
|
|
|
|
# by the common <!-- HIDE technique. |
220
|
|
|
|
|
|
|
# See https://github.com/nevesenin/javascript-packer-perl/issues/1#issuecomment-4356790 |
221
|
0
|
|
|
|
|
0
|
my $options = { |
222
|
|
|
|
|
|
|
remove_comments => 1, |
223
|
|
|
|
|
|
|
remove_newlines => 0, |
224
|
|
|
|
|
|
|
do_stylesheet => 'minify' |
225
|
|
|
|
|
|
|
}; |
226
|
0
|
0
|
|
|
|
0
|
if($optimise_content >= 2) { |
227
|
0
|
|
|
|
|
0
|
$options->{do_javascript} = 'best'; |
228
|
0
|
|
|
|
|
0
|
$body =~ s/(<script.*?>)\s*<!--/$1/gi; |
229
|
0
|
|
|
|
|
0
|
$body =~ s/\/\/-->\s*<\/script>/<\/script>/gi; |
230
|
0
|
|
|
|
|
0
|
$body =~ s/(<script.*?>)\s+/$1/gi; |
231
|
|
|
|
|
|
|
} |
232
|
0
|
|
|
|
|
0
|
$body = HTML::Packer->init()->minify(\$body, $options); |
233
|
0
|
0
|
|
|
|
0
|
if($optimise_content >= 2) { |
234
|
|
|
|
|
|
|
# Change document.write("a"); document.write("b") |
235
|
|
|
|
|
|
|
# into document.write("a"+"b"); |
236
|
0
|
|
|
|
|
0
|
while(1) { |
237
|
0
|
|
|
|
|
0
|
$body =~ s/<script\s*?type\s*?=\s*?"text\/javascript"\s*?>(.*?)document\.write\((.+?)\);\s*?document\.write\((.+?)\)/<script type="text\/JavaScript">${1}document.write($2+$3)/igs; |
238
|
0
|
|
|
|
|
0
|
$newlength = length($body); |
239
|
0
|
0
|
|
|
|
0
|
last if ($newlength >= $oldlength); |
240
|
0
|
|
|
|
|
0
|
$oldlength = $newlength; |
241
|
|
|
|
|
|
|
} |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
} |
244
|
0
|
0
|
|
|
|
0
|
if($lint_content) { |
245
|
0
|
|
|
|
|
0
|
require HTML::Lint; |
246
|
0
|
|
|
|
|
0
|
HTML::Lint->import; |
247
|
|
|
|
|
|
|
|
248
|
0
|
0
|
|
|
|
0
|
if($logger) { |
249
|
0
|
|
|
|
|
0
|
$logger->trace('Lint'); |
250
|
|
|
|
|
|
|
} |
251
|
0
|
|
|
|
|
0
|
my $lint = HTML::Lint->new(); |
252
|
0
|
|
|
|
|
0
|
$lint->parse($body); |
253
|
0
|
|
|
|
|
0
|
$lint->eof(); |
254
|
|
|
|
|
|
|
|
255
|
0
|
0
|
|
|
|
0
|
if($lint->errors) { |
256
|
0
|
|
|
|
|
0
|
$headers = 'Status: 500 Internal Server Error'; |
257
|
0
|
|
|
|
|
0
|
@o = ('Content-type: text/plain'); |
258
|
0
|
|
|
|
|
0
|
$body = ''; |
259
|
0
|
|
|
|
|
0
|
foreach my $error ($lint->errors) { |
260
|
0
|
|
|
|
|
0
|
my $errtext = $error->where() . ': ' . $error->errtext() . "\n"; |
261
|
0
|
0
|
|
|
|
0
|
if($logger) { |
262
|
0
|
|
|
|
|
0
|
$logger->warn($errtext); |
263
|
|
|
|
|
|
|
} else { |
264
|
0
|
|
|
|
|
0
|
warn($errtext); |
265
|
|
|
|
|
|
|
} |
266
|
0
|
|
|
|
|
0
|
$body .= $errtext; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
0
|
0
|
0
|
|
|
0
|
if(defined($headers) && ($headers =~ /^Status: (\d+)/m)) { |
|
|
0
|
|
|
|
|
|
273
|
0
|
|
|
|
|
0
|
$status = $1; |
274
|
|
|
|
|
|
|
} elsif($info) { |
275
|
0
|
|
|
|
|
0
|
$status = $info->status(); |
276
|
|
|
|
|
|
|
} else { |
277
|
0
|
|
|
|
|
0
|
$status = 200; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
0
|
0
|
|
|
|
0
|
if($logger) { |
281
|
0
|
|
|
|
|
0
|
$logger->debug("Initial status = $status"); |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
# Generate the eTag before compressing, since the compressed data |
285
|
|
|
|
|
|
|
# includes the mtime field which changes thus causing a different |
286
|
|
|
|
|
|
|
# Etag to be generated |
287
|
0
|
0
|
0
|
|
|
0
|
if($ENV{'SERVER_PROTOCOL'} && |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
288
|
|
|
|
|
|
|
(($ENV{'SERVER_PROTOCOL'} eq 'HTTP/1.1') || ($ENV{'SERVER_PROTOCOL'} eq 'HTTP/2.0')) && |
289
|
|
|
|
|
|
|
$generate_etag && defined($body)) { |
290
|
|
|
|
|
|
|
# encode to avoid "Wide character in subroutine entry" |
291
|
0
|
|
|
|
|
0
|
require Encode; |
292
|
0
|
|
|
|
|
0
|
$encode_loaded = 1; |
293
|
0
|
|
|
|
|
0
|
$etag = '"' . Digest::MD5->new->add(Encode::encode_utf8($body))->hexdigest() . '"'; |
294
|
0
|
0
|
0
|
|
|
0
|
if($ENV{'HTTP_IF_NONE_MATCH'} && $generate_304 && ($status == 200)) { |
|
|
|
0
|
|
|
|
|
295
|
0
|
0
|
|
|
|
0
|
if($logger) { |
296
|
0
|
|
|
|
|
0
|
$logger->debug("Compare $ENV{HTTP_IF_NONE_MATCH} with $etag"); |
297
|
|
|
|
|
|
|
} |
298
|
0
|
0
|
|
|
|
0
|
if($ENV{'HTTP_IF_NONE_MATCH'} eq $etag) { |
|
|
0
|
|
|
|
|
|
299
|
0
|
|
|
|
|
0
|
push @o, "Status: 304 Not Modified"; |
300
|
0
|
|
|
|
|
0
|
$send_body = 0; |
301
|
0
|
|
|
|
|
0
|
$status = 304; |
302
|
0
|
0
|
|
|
|
0
|
if($logger) { |
303
|
0
|
|
|
|
|
0
|
$logger->debug('Set status to 304'); |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
} elsif($logger) { |
306
|
0
|
|
|
|
|
0
|
$logger->debug(diff(\$body, \$cache->get(_generate_key()))); |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
0
|
|
|
|
|
0
|
my $encoding = _should_gzip(); |
312
|
0
|
|
|
|
|
0
|
my $unzipped_body = $body; |
313
|
|
|
|
|
|
|
|
314
|
0
|
0
|
|
|
|
0
|
if(defined($unzipped_body)) { |
315
|
0
|
0
|
|
|
|
0
|
my $range = $ENV{'Range'} ? $ENV{'Range'} : $ENV{'HTTP_RANGE'}; |
316
|
|
|
|
|
|
|
|
317
|
0
|
0
|
0
|
|
|
0
|
if($range && !$cache) { |
318
|
|
|
|
|
|
|
# TODO: Partials |
319
|
0
|
0
|
|
|
|
0
|
if($range =~ /^bytes=(\d*)-(\d*)/) { |
320
|
0
|
0
|
0
|
|
|
0
|
if($1 && $2) { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
321
|
0
|
|
|
|
|
0
|
$body = substr($body, $1, $2-$1); |
322
|
|
|
|
|
|
|
} elsif($1) { |
323
|
0
|
|
|
|
|
0
|
$body = substr($body, $1); |
324
|
|
|
|
|
|
|
} elsif($2) { |
325
|
0
|
|
|
|
|
0
|
$body = substr($body, 0, $2); |
326
|
|
|
|
|
|
|
} |
327
|
0
|
|
|
|
|
0
|
$unzipped_body = $body; |
328
|
0
|
|
|
|
|
0
|
$status = 206; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
} |
331
|
0
|
|
|
|
|
0
|
_compress({ encoding => $encoding }); |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
0
|
0
|
|
|
|
0
|
if($cache) { |
|
|
0
|
|
|
|
|
|
335
|
0
|
|
|
|
|
0
|
require Storable; |
336
|
|
|
|
|
|
|
|
337
|
0
|
|
|
|
|
0
|
my $cache_hash; |
338
|
0
|
|
|
|
|
0
|
my $key = _generate_key(); |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# Cache unzipped version |
341
|
0
|
0
|
|
|
|
0
|
if(!defined($body)) { |
342
|
0
|
0
|
|
|
|
0
|
if($send_body) { |
343
|
0
|
|
|
|
|
0
|
$cobject = $cache->get_object($key); |
344
|
0
|
0
|
|
|
|
0
|
if(defined($cobject)) { |
|
|
0
|
|
|
|
|
|
345
|
0
|
|
|
|
|
0
|
$cache_hash = Storable::thaw($cobject->value()); |
346
|
0
|
|
|
|
|
0
|
$headers = $cache_hash->{'headers'}; |
347
|
0
|
|
|
|
|
0
|
_set_content_type($headers); |
348
|
0
|
|
|
|
|
0
|
@o = ("X-CGI-Buffer-$VERSION: Hit"); |
349
|
0
|
0
|
|
|
|
0
|
if($info) { |
350
|
0
|
|
|
|
|
0
|
my $host_name = $info->host_name(); |
351
|
0
|
|
|
|
|
0
|
push @o, "X-Cache: HIT from $host_name"; |
352
|
0
|
|
|
|
|
0
|
push @o, "X-Cache-Lookup: HIT from $host_name"; |
353
|
|
|
|
|
|
|
} else { |
354
|
0
|
|
|
|
|
0
|
push @o, 'X-Cache: HIT'; |
355
|
0
|
|
|
|
|
0
|
push @o, 'X-Cache-Lookup: HIT'; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
} elsif($logger) { |
358
|
0
|
|
|
|
|
0
|
$logger->warn("Error retrieving data for key $key"); |
359
|
|
|
|
|
|
|
} else { |
360
|
0
|
|
|
|
|
0
|
carp(__PACKAGE__, ": error retrieving data for key $key"); |
361
|
|
|
|
|
|
|
} |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# Nothing has been output yet, so we can check if it's |
365
|
|
|
|
|
|
|
# OK to send 304 if possible |
366
|
0
|
0
|
0
|
|
|
0
|
if($send_body && $ENV{'SERVER_PROTOCOL'} && |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
367
|
|
|
|
|
|
|
(($ENV{'SERVER_PROTOCOL'} eq 'HTTP/1.1') || ($ENV{'SERVER_PROTOCOL'} eq 'HTTP/2.0')) && |
368
|
|
|
|
|
|
|
$generate_304 && ($status == 200)) { |
369
|
0
|
0
|
|
|
|
0
|
if($ENV{'HTTP_IF_MODIFIED_SINCE'}) { |
370
|
|
|
|
|
|
|
_check_modified_since({ |
371
|
0
|
|
|
|
|
0
|
since => $ENV{'HTTP_IF_MODIFIED_SINCE'}, |
372
|
|
|
|
|
|
|
modified => $cobject->created_at() |
373
|
|
|
|
|
|
|
}); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
} |
376
|
0
|
0
|
0
|
|
|
0
|
if($send_body && ($status == 200) && defined($cache_hash)) { |
|
|
|
0
|
|
|
|
|
377
|
0
|
|
|
|
|
0
|
$body = $cache_hash->{'body'}; |
378
|
0
|
0
|
|
|
|
0
|
if(!defined($body)) { |
379
|
|
|
|
|
|
|
# Panic |
380
|
0
|
|
|
|
|
0
|
$headers = 'Status: 500 Internal Server Error'; |
381
|
0
|
|
|
|
|
0
|
@o = ('Content-type: text/plain'); |
382
|
0
|
|
|
|
|
0
|
$body = "Can't retrieve body for key $key, cache_hash contains:\n"; |
383
|
0
|
|
|
|
|
0
|
foreach my $k (keys %{$cache_hash}) { |
|
0
|
|
|
|
|
0
|
|
384
|
0
|
|
|
|
|
0
|
$body .= "\t$k\n"; |
385
|
|
|
|
|
|
|
} |
386
|
0
|
|
|
|
|
0
|
$cache->remove($key); |
387
|
0
|
0
|
|
|
|
0
|
if($logger) { |
388
|
0
|
|
|
|
|
0
|
$logger->error("Can't retrieve body for key $key"); |
389
|
0
|
|
|
|
|
0
|
$logger->warn($body); |
390
|
|
|
|
|
|
|
} else { |
391
|
0
|
|
|
|
|
0
|
carp "Can't retrieve body for key $key"; |
392
|
0
|
|
|
|
|
0
|
warn($body); |
393
|
|
|
|
|
|
|
} |
394
|
0
|
|
|
|
|
0
|
$send_body = 0; |
395
|
0
|
|
|
|
|
0
|
$status = 500; |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
} |
398
|
0
|
0
|
0
|
|
|
0
|
if($send_body && $ENV{'SERVER_PROTOCOL'} && |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
399
|
|
|
|
|
|
|
(($ENV{'SERVER_PROTOCOL'} eq 'HTTP/1.1') || ($ENV{'SERVER_PROTOCOL'} eq 'HTTP/2.0')) && |
400
|
|
|
|
|
|
|
($status == 200)) { |
401
|
0
|
0
|
|
|
|
0
|
if($ENV{'HTTP_IF_NONE_MATCH'}) { |
402
|
0
|
0
|
|
|
|
0
|
if(!defined($etag)) { |
403
|
0
|
0
|
|
|
|
0
|
unless($encode_loaded) { |
404
|
0
|
|
|
|
|
0
|
require Encode; |
405
|
0
|
|
|
|
|
0
|
$encode_loaded = 1; |
406
|
|
|
|
|
|
|
} |
407
|
0
|
|
|
|
|
0
|
$etag = '"' . Digest::MD5->new->add(Encode::encode_utf8($body))->hexdigest() . '"'; |
408
|
|
|
|
|
|
|
} |
409
|
0
|
0
|
0
|
|
|
0
|
if($logger && $generate_304) { |
410
|
0
|
|
|
|
|
0
|
$logger->debug("Compare etags $ENV{HTTP_IF_NONE_MATCH} and $etag"); |
411
|
|
|
|
|
|
|
} |
412
|
0
|
0
|
0
|
|
|
0
|
if(($ENV{'HTTP_IF_NONE_MATCH'} eq $etag) && $generate_304) { |
413
|
0
|
|
|
|
|
0
|
push @o, "Status: 304 Not Modified"; |
414
|
0
|
|
|
|
|
0
|
$status = 304; |
415
|
0
|
|
|
|
|
0
|
$send_body = 0; |
416
|
0
|
0
|
|
|
|
0
|
if($logger) { |
417
|
0
|
|
|
|
|
0
|
$logger->debug('Set status to 304'); |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
} |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
} |
422
|
0
|
0
|
|
|
|
0
|
if($status == 200) { |
423
|
0
|
|
|
|
|
0
|
$encoding = _should_gzip(); |
424
|
0
|
0
|
|
|
|
0
|
if($send_body) { |
425
|
0
|
0
|
0
|
|
|
0
|
if($generate_etag && !defined($etag) && ((!defined($headers)) || ($headers !~ /^ETag: /m))) { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
426
|
0
|
|
|
|
|
0
|
$etag = '"' . Digest::MD5->new->add(Encode::encode_utf8($body))->hexdigest() . '"'; |
427
|
|
|
|
|
|
|
} |
428
|
0
|
|
|
|
|
0
|
_compress({ encoding => $encoding }); |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
} |
431
|
0
|
|
|
|
|
0
|
my $cannot_304 = !$generate_304; |
432
|
0
|
0
|
|
|
|
0
|
unless($etag) { |
433
|
0
|
0
|
0
|
|
|
0
|
if(defined($headers) && ($headers =~ /^ETag: "([a-z0-9]{32})"/m)) { |
434
|
0
|
|
|
|
|
0
|
$etag = $1; |
435
|
|
|
|
|
|
|
} else { |
436
|
0
|
|
|
|
|
0
|
$etag = $cache_hash->{'etag'}; |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
} |
439
|
0
|
0
|
0
|
|
|
0
|
if($ENV{'HTTP_IF_NONE_MATCH'} && $send_body && ($status != 304) && $generate_304) { |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
440
|
0
|
0
|
|
|
|
0
|
if($logger) { |
441
|
0
|
|
|
|
|
0
|
$logger->debug("Compare $ENV{HTTP_IF_NONE_MATCH} with $etag"); |
442
|
|
|
|
|
|
|
} |
443
|
0
|
0
|
0
|
|
|
0
|
if(defined($etag) && ($etag eq $ENV{'HTTP_IF_NONE_MATCH'}) && ($status == 200)) { |
|
|
|
0
|
|
|
|
|
444
|
0
|
|
|
|
|
0
|
push @o, "Status: 304 Not Modified"; |
445
|
0
|
|
|
|
|
0
|
$send_body = 0; |
446
|
0
|
|
|
|
|
0
|
$status = 304; |
447
|
0
|
0
|
|
|
|
0
|
if($logger) { |
448
|
0
|
|
|
|
|
0
|
$logger->debug('Set status to 304'); |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
} else { |
451
|
0
|
|
|
|
|
0
|
$cannot_304 = 1; |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
} |
454
|
0
|
0
|
|
|
|
0
|
if($cobject) { |
455
|
0
|
0
|
0
|
|
|
0
|
if($ENV{'HTTP_IF_MODIFIED_SINCE'} && ($status != 304) && !$cannot_304) { |
|
|
|
0
|
|
|
|
|
456
|
|
|
|
|
|
|
_check_modified_since({ |
457
|
0
|
|
|
|
|
0
|
since => $ENV{'HTTP_IF_MODIFIED_SINCE'}, |
458
|
|
|
|
|
|
|
modified => $cobject->created_at() |
459
|
|
|
|
|
|
|
}); |
460
|
|
|
|
|
|
|
} |
461
|
0
|
0
|
0
|
|
|
0
|
if(($status == 200) && $generate_last_modified) { |
462
|
0
|
0
|
|
|
|
0
|
if($logger) { |
463
|
0
|
|
|
|
|
0
|
$logger->debug('Set Last-Modified to ', HTTP::Date::time2str($cobject->created_at())); |
464
|
|
|
|
|
|
|
} |
465
|
0
|
|
|
|
|
0
|
push @o, "Last-Modified: " . HTTP::Date::time2str($cobject->created_at()); |
466
|
|
|
|
|
|
|
} |
467
|
|
|
|
|
|
|
} |
468
|
|
|
|
|
|
|
} else { |
469
|
|
|
|
|
|
|
# Not in the server side cache |
470
|
0
|
0
|
|
|
|
0
|
if($status == 200) { |
471
|
0
|
0
|
|
|
|
0
|
unless($cache_age) { |
472
|
|
|
|
|
|
|
# It would be great if CHI::set() |
473
|
|
|
|
|
|
|
# allowed the time to be 'lru' for least |
474
|
|
|
|
|
|
|
# recently used. |
475
|
0
|
|
|
|
|
0
|
$cache_age = '10 minutes'; |
476
|
|
|
|
|
|
|
} |
477
|
0
|
|
|
|
|
0
|
$cache_hash->{'body'} = $unzipped_body; |
478
|
0
|
0
|
0
|
|
|
0
|
if(@o && defined($o[0])) { |
|
|
0
|
0
|
|
|
|
|
479
|
|
|
|
|
|
|
# Remember, we're storing the UNzipped |
480
|
|
|
|
|
|
|
# version in the cache |
481
|
0
|
|
|
|
|
0
|
my $c; |
482
|
0
|
0
|
0
|
|
|
0
|
if(defined($headers) && length($headers)) { |
483
|
0
|
|
|
|
|
0
|
$c = $headers . "\r\n" . join("\r\n", @o); |
484
|
|
|
|
|
|
|
} else { |
485
|
0
|
|
|
|
|
0
|
$c = join("\r\n", @o); |
486
|
|
|
|
|
|
|
} |
487
|
0
|
|
|
|
|
0
|
$c =~ s/^Content-Encoding: .+$//mg; |
488
|
0
|
|
|
|
|
0
|
$c =~ s/^Vary: Accept-Encoding.*\r?$//mg; |
489
|
0
|
|
|
|
|
0
|
$c =~ s/\n+/\n/gs; |
490
|
0
|
0
|
|
|
|
0
|
if(length($c)) { |
491
|
0
|
|
|
|
|
0
|
$cache_hash->{'headers'} = $c; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
} elsif(defined($headers) && length($headers)) { |
494
|
0
|
|
|
|
|
0
|
$headers =~ s/^Content-Encoding: .+$//mg; |
495
|
0
|
|
|
|
|
0
|
$headers =~ s/^Vary: Accept-Encoding.*\r?$//mg; |
496
|
0
|
|
|
|
|
0
|
$headers =~ s/\n+/\n/gs; |
497
|
0
|
0
|
|
|
|
0
|
if(length($headers)) { |
498
|
0
|
|
|
|
|
0
|
$cache_hash->{'headers'} = $headers; |
499
|
|
|
|
|
|
|
} |
500
|
|
|
|
|
|
|
} |
501
|
0
|
0
|
0
|
|
|
0
|
if($generate_etag && defined($etag)) { |
502
|
0
|
|
|
|
|
0
|
$cache_hash->{'etag'} = $etag; |
503
|
|
|
|
|
|
|
} |
504
|
|
|
|
|
|
|
# TODO: Support the Expires header |
505
|
|
|
|
|
|
|
# if($headers !~ /^Expires: /m))) { |
506
|
|
|
|
|
|
|
# } |
507
|
0
|
0
|
|
|
|
0
|
if($logger) { |
508
|
0
|
|
|
|
|
0
|
$logger->debug("Store $key in the cache, age = $cache_age ", length($cache_hash->{'body'}), ' bytes'); |
509
|
|
|
|
|
|
|
} |
510
|
0
|
|
|
|
|
0
|
$cache->set($key, Storable::freeze($cache_hash), $cache_age); |
511
|
0
|
0
|
|
|
|
0
|
if($generate_last_modified) { |
512
|
0
|
|
|
|
|
0
|
$cobject = $cache->get_object($key); |
513
|
0
|
0
|
|
|
|
0
|
if(defined($cobject)) { |
514
|
0
|
|
|
|
|
0
|
push @o, "Last-Modified: " . HTTP::Date::time2str($cobject->created_at()); |
515
|
|
|
|
|
|
|
} else { |
516
|
0
|
|
|
|
|
0
|
push @o, "Last-Modified: " . HTTP::Date::time2str(time); |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
} |
520
|
0
|
0
|
|
|
|
0
|
if($info) { |
521
|
0
|
|
|
|
|
0
|
my $host_name = $info->host_name(); |
522
|
0
|
0
|
|
|
|
0
|
if(defined($x_cache)) { |
523
|
0
|
|
|
|
|
0
|
push @o, "X-Cache: $x_cache from $host_name"; |
524
|
|
|
|
|
|
|
} else { |
525
|
0
|
|
|
|
|
0
|
push @o, "X-Cache: MISS from $host_name"; |
526
|
|
|
|
|
|
|
} |
527
|
0
|
|
|
|
|
0
|
push @o, "X-Cache-Lookup: MISS from $host_name"; |
528
|
|
|
|
|
|
|
} else { |
529
|
0
|
0
|
|
|
|
0
|
if(defined($x_cache)) { |
530
|
0
|
|
|
|
|
0
|
push @o, "X-Cache: $x_cache"; |
531
|
|
|
|
|
|
|
} else { |
532
|
0
|
|
|
|
|
0
|
push @o, 'X-Cache: MISS'; |
533
|
|
|
|
|
|
|
} |
534
|
0
|
|
|
|
|
0
|
push @o, 'X-Cache-Lookup: MISS'; |
535
|
|
|
|
|
|
|
} |
536
|
0
|
|
|
|
|
0
|
push @o, "X-CGI-Buffer-$VERSION: Miss"; |
537
|
|
|
|
|
|
|
} |
538
|
|
|
|
|
|
|
# We don't need it any more, so give Perl a chance to |
539
|
|
|
|
|
|
|
# tidy it up seeing as we're in the destructor |
540
|
0
|
|
|
|
|
0
|
$cache = undef; |
541
|
|
|
|
|
|
|
} elsif($info) { |
542
|
0
|
|
|
|
|
0
|
my $host_name = $info->host_name(); |
543
|
0
|
|
|
|
|
0
|
push @o, ("X-Cache: MISS from $host_name", "X-Cache-Lookup: MISS from $host_name"); |
544
|
0
|
0
|
|
|
|
0
|
if($generate_last_modified) { |
545
|
0
|
0
|
|
|
|
0
|
if(my $age = _my_age()) { |
546
|
0
|
|
|
|
|
0
|
push @o, 'Last-Modified: ' . HTTP::Date::time2str($age); |
547
|
|
|
|
|
|
|
} |
548
|
|
|
|
|
|
|
} |
549
|
0
|
0
|
0
|
|
|
0
|
if($ENV{'HTTP_IF_MODIFIED_SINCE'} && ($status != 304) && $generate_304) { |
|
|
|
0
|
|
|
|
|
550
|
|
|
|
|
|
|
_check_modified_since({ |
551
|
0
|
|
|
|
|
0
|
since => $ENV{'HTTP_IF_MODIFIED_SINCE'}, |
552
|
|
|
|
|
|
|
modified => _my_age() |
553
|
|
|
|
|
|
|
}); |
554
|
|
|
|
|
|
|
} |
555
|
|
|
|
|
|
|
} else { |
556
|
0
|
|
|
|
|
0
|
push @o, ('X-Cache: MISS', 'X-Cache-Lookup: MISS'); |
557
|
|
|
|
|
|
|
} |
558
|
0
|
0
|
0
|
|
|
0
|
if($generate_etag && ((!defined($headers)) || ($headers !~ /^ETag: /m))) { |
|
|
|
0
|
|
|
|
|
559
|
0
|
0
|
0
|
|
|
0
|
if(defined($etag)) { |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
560
|
0
|
|
|
|
|
0
|
push @o, "ETag: $etag"; |
561
|
0
|
0
|
|
|
|
0
|
if($logger) { |
562
|
0
|
|
|
|
|
0
|
$logger->debug("Set ETag to $etag"); |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
} elsif($logger && (($status == 200) || $status == 304) && $body && !is_cached()) { |
565
|
0
|
|
|
|
|
0
|
$logger->warn("BUG: ETag not generated, status $status"); |
566
|
|
|
|
|
|
|
} |
567
|
|
|
|
|
|
|
} |
568
|
|
|
|
|
|
|
|
569
|
0
|
|
|
|
|
0
|
my $body_length; |
570
|
0
|
0
|
|
|
|
0
|
if(defined($body)) { |
571
|
0
|
0
|
|
|
|
0
|
if(utf8::is_utf8($body)) { |
572
|
0
|
|
|
|
|
0
|
utf8::encode($body); |
573
|
|
|
|
|
|
|
} |
574
|
0
|
|
|
|
|
0
|
$body_length = length($body); |
575
|
|
|
|
|
|
|
} else { |
576
|
0
|
|
|
|
|
0
|
$body_length = 0; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
0
|
0
|
0
|
|
|
0
|
if(defined($headers) && length($headers)) { |
580
|
|
|
|
|
|
|
# Put the original headers first, then those generated within |
581
|
|
|
|
|
|
|
# CGI::Buffer |
582
|
0
|
|
|
|
|
0
|
unshift @o, split(/\r\n/, $headers); |
583
|
0
|
0
|
0
|
|
|
0
|
if($body && $send_body) { |
584
|
0
|
0
|
|
|
|
0
|
if(scalar(grep(/^Content-Length: \d/, @o)) == 0) { |
585
|
0
|
|
|
|
|
0
|
push @o, "Content-Length: $body_length"; |
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
} |
588
|
0
|
0
|
|
|
|
0
|
if(scalar(grep(/^Status: \d/, @o)) == 0) { |
589
|
0
|
|
|
|
|
0
|
require HTTP::Status; |
590
|
0
|
|
|
|
|
0
|
HTTP::Status->import(); |
591
|
|
|
|
|
|
|
|
592
|
0
|
|
|
|
|
0
|
push @o, "Status: $status " . HTTP::Status::status_message($status); |
593
|
|
|
|
|
|
|
} |
594
|
|
|
|
|
|
|
} else { |
595
|
0
|
|
|
|
|
0
|
push @o, "X-CGI-Buffer-$VERSION: No headers"; |
596
|
|
|
|
|
|
|
} |
597
|
|
|
|
|
|
|
|
598
|
0
|
0
|
0
|
|
|
0
|
if($body_length && $send_body) { |
599
|
0
|
|
|
|
|
0
|
push @o, ('', $body); |
600
|
|
|
|
|
|
|
} |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXX |
603
|
0
|
0
|
|
|
|
0
|
if(0) { |
604
|
|
|
|
|
|
|
# This code helps to debug Wide character prints |
605
|
|
|
|
|
|
|
my $wideCharWarningsIssued = 0; |
606
|
|
|
|
|
|
|
my $widemess; |
607
|
|
|
|
|
|
|
$SIG{__WARN__} = sub { |
608
|
|
|
|
|
|
|
$wideCharWarningsIssued += "@_" =~ /Wide character in .../; |
609
|
|
|
|
|
|
|
$widemess = "@_"; |
610
|
|
|
|
|
|
|
if($logger) { |
611
|
|
|
|
|
|
|
$logger->fatal($widemess); |
612
|
|
|
|
|
|
|
my $i = 1; |
613
|
|
|
|
|
|
|
$logger->trace('Stack Trace'); |
614
|
|
|
|
|
|
|
while((my @call_details = (caller($i++)))) { |
615
|
|
|
|
|
|
|
$logger->trace($call_details[1] . ':' . $call_details[2] . ' in function ' . $call_details[3]); |
616
|
|
|
|
|
|
|
} |
617
|
|
|
|
|
|
|
} |
618
|
|
|
|
|
|
|
CORE::warn(@_); # call the builtin warn as usual |
619
|
|
|
|
|
|
|
}; |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
if(scalar @o) { |
622
|
|
|
|
|
|
|
print join("\r\n", @o); |
623
|
|
|
|
|
|
|
if($wideCharWarningsIssued) { |
624
|
|
|
|
|
|
|
my $mess = join("\r\n", @o); |
625
|
|
|
|
|
|
|
$mess =~ /[^\x00-\xFF]/; |
626
|
|
|
|
|
|
|
open(my $fout, '>>', '/tmp/NJH'); |
627
|
|
|
|
|
|
|
print $fout "$widemess:\n"; |
628
|
|
|
|
|
|
|
print $fout $mess; |
629
|
|
|
|
|
|
|
print $fout 'x' x 40, "\n"; |
630
|
|
|
|
|
|
|
close $fout; |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
} |
633
|
0
|
|
|
|
|
0
|
} elsif(scalar @o) { |
634
|
0
|
|
|
|
|
0
|
print join("\r\n", @o); |
635
|
|
|
|
|
|
|
} |
636
|
|
|
|
|
|
|
# XXXXXXXXXXXXXXXXXXXXXXX |
637
|
|
|
|
|
|
|
|
638
|
0
|
0
|
0
|
|
|
0
|
if((!$send_body) || !defined($body)) { |
639
|
0
|
|
|
|
|
0
|
print "\r\n\r\n"; |
640
|
|
|
|
|
|
|
} |
641
|
|
|
|
|
|
|
} |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
sub _check_modified_since { |
644
|
0
|
0
|
|
0
|
|
0
|
if($logger) { |
645
|
0
|
|
|
|
|
0
|
$logger->trace('In _check_modified_since'); |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
|
648
|
0
|
0
|
|
|
|
0
|
if(!$generate_304) { |
649
|
0
|
|
|
|
|
0
|
return; |
650
|
|
|
|
|
|
|
} |
651
|
0
|
|
|
|
|
0
|
my $params = shift; |
652
|
|
|
|
|
|
|
|
653
|
0
|
0
|
|
|
|
0
|
if(!defined($$params{since})) { |
654
|
0
|
|
|
|
|
0
|
return; |
655
|
|
|
|
|
|
|
} |
656
|
0
|
|
|
|
|
0
|
my $s = HTTP::Date::str2time($$params{since}); |
657
|
0
|
0
|
|
|
|
0
|
if(!defined($s)) { |
658
|
|
|
|
|
|
|
# IF_MODIFIED_SINCE isn't a valid data |
659
|
0
|
|
|
|
|
0
|
return; |
660
|
|
|
|
|
|
|
} |
661
|
|
|
|
|
|
|
|
662
|
0
|
|
|
|
|
0
|
my $age = _my_age(); |
663
|
0
|
0
|
|
|
|
0
|
if(!defined($age)) { |
664
|
0
|
|
|
|
|
0
|
return; |
665
|
|
|
|
|
|
|
} |
666
|
0
|
0
|
|
|
|
0
|
if($age > $s) { |
667
|
0
|
0
|
|
|
|
0
|
if($logger) { |
668
|
0
|
|
|
|
|
0
|
$logger->debug('_check_modified_since: script has been modified'); |
669
|
|
|
|
|
|
|
} |
670
|
|
|
|
|
|
|
# Script has been updated so it may produce different output |
671
|
0
|
|
|
|
|
0
|
return; |
672
|
|
|
|
|
|
|
} |
673
|
|
|
|
|
|
|
|
674
|
0
|
0
|
|
|
|
0
|
if($logger) { |
675
|
0
|
|
|
|
|
0
|
$logger->debug("_check_modified_since: Compare $$params{modified} with $s"); |
676
|
|
|
|
|
|
|
} |
677
|
0
|
0
|
|
|
|
0
|
if($$params{modified} <= $s) { |
678
|
0
|
|
|
|
|
0
|
push @o, "Status: 304 Not Modified"; |
679
|
0
|
|
|
|
|
0
|
$status = 304; |
680
|
0
|
|
|
|
|
0
|
$send_body = 0; |
681
|
0
|
0
|
|
|
|
0
|
if($logger) { |
682
|
0
|
|
|
|
|
0
|
$logger->debug('Set status to 304'); |
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
} |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
# Reduce output, e.g. remove superfluous white-space. |
688
|
|
|
|
|
|
|
sub _optimise_content { |
689
|
|
|
|
|
|
|
# FIXME: regex bad, HTML parser good |
690
|
|
|
|
|
|
|
# Regexp::List - wow! |
691
|
0
|
|
|
0
|
|
0
|
$body =~ s/(((\s+|\r)\n|\n(\s+|\+)))/\n/g; |
692
|
|
|
|
|
|
|
# $body =~ s/\r\n/\n/gs; |
693
|
|
|
|
|
|
|
# $body =~ s/\s+\n/\n/gs; |
694
|
|
|
|
|
|
|
# $body =~ s/\n+/\n/gs; |
695
|
|
|
|
|
|
|
# $body =~ s/\n\s+|\s+\n/\n/g; |
696
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/div\>\s+\<div/\<\/div\>\<div/gis; |
697
|
|
|
|
|
|
|
# $body =~ s/\<\/p\>\s\<\/div/\<\/p\>\<\/div/gis; |
698
|
|
|
|
|
|
|
# $body =~ s/\<div\>\s+/\<div\>/gis; # Remove spaces after <div> |
699
|
0
|
|
|
|
|
0
|
$body =~ s/(<div>\s+|\s+<div>)/<div>/gis; |
700
|
0
|
|
|
|
|
0
|
$body =~ s/\s+<\/div\>/\<\/div\>/gis; # Remove spaces before </div> |
701
|
0
|
|
|
|
|
0
|
$body =~ s/\s+\<p\>|\<p\>\s+/\<p\>/im; # TODO <p class= |
702
|
0
|
|
|
|
|
0
|
$body =~ s/\s+\<\/p\>|\<\/p\>\s+/\<\/p\>/gis; |
703
|
0
|
|
|
|
|
0
|
$body =~ s/<html>\s+<head>/<html><head>/is; |
704
|
0
|
|
|
|
|
0
|
$body =~ s/\s*<\/head>\s+<body>\s*/<\/head><body>/is; |
705
|
0
|
|
|
|
|
0
|
$body =~ s/<html>\s+<body>/<html><body>/is; |
706
|
0
|
|
|
|
|
0
|
$body =~ s/<body>\s+/<body>/is; |
707
|
0
|
|
|
|
|
0
|
$body =~ s/\s+\<\/html/\<\/html/is; |
708
|
0
|
|
|
|
|
0
|
$body =~ s/\s+\<\/body/\<\/body/is; |
709
|
0
|
|
|
|
|
0
|
$body =~ s/\s(\<.+?\>\s\<.+?\>)/$1/; |
710
|
|
|
|
|
|
|
# $body =~ s/(\<.+?\>\s\<.+?\>)\s/$1/g; |
711
|
0
|
|
|
|
|
0
|
$body =~ s/\<p\>\s/\<p\>/gi; |
712
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/p\>\s\<p\>/\<\/p\>\<p\>/gi; |
713
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/tr\>\s\<tr\>/\<\/tr\>\<tr\>/gi; |
714
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/td\>\s\<\/tr\>/\<\/td\>\<\/tr\>/gi; |
715
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/td\>\s*\<td\>/\<\/td\>\<td\>/gis; |
716
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/tr\>\s\<\/table\>/\<\/tr\>\<\/table\>/gi; |
717
|
0
|
|
|
|
|
0
|
$body =~ s/\<br\s?\/?\>\s?\<p\>/\<p\>/gi; |
718
|
0
|
|
|
|
|
0
|
$body =~ s/\<br\>\s/\<br\>/gi; |
719
|
0
|
|
|
|
|
0
|
$body =~ s/\s+\<br/\<br/gi; |
720
|
0
|
|
|
|
|
0
|
$body =~ s/\<br\s?\/\>\s/\<br \/\>/gi; |
721
|
0
|
|
|
|
|
0
|
$body =~ s/[ \t]+/ /gs; # Remove duplicate space, don't use \s+ it breaks JavaScript |
722
|
0
|
|
|
|
|
0
|
$body =~ s/\s\<p\>/\<p\>/gi; |
723
|
0
|
|
|
|
|
0
|
$body =~ s/\s\<script/\<script/gi; |
724
|
0
|
|
|
|
|
0
|
$body =~ s/(<script>\s|\s<script>)/<script>/gis; |
725
|
0
|
|
|
|
|
0
|
$body =~ s/(<\/script>\s|\s<\/script>)/<\/script>/gis; |
726
|
0
|
|
|
|
|
0
|
$body =~ s/\<td\>\s/\<td\>/gi; |
727
|
0
|
|
|
|
|
0
|
$body =~ s/\s+\<a\shref="(.+?)"\>\s?/ <a href="$1">/gis; |
728
|
0
|
|
|
|
|
0
|
$body =~ s/\s?<a\shref=\s"(.+?)"\>/ <a href="$1">/gis; |
729
|
0
|
|
|
|
|
0
|
$body =~ s/\s+<\/a\>\s+/<\/a> /gis; |
730
|
0
|
|
|
|
|
0
|
$body =~ s/(\s?<hr>\s+|\s+<hr>\s?)/<hr>/gis; |
731
|
|
|
|
|
|
|
# $body =~ s/\s<hr>/<hr>/gis; |
732
|
|
|
|
|
|
|
# $body =~ s/<hr>\s/<hr>/gis; |
733
|
0
|
|
|
|
|
0
|
$body =~ s/<\/li>\s+<li>/<\/li><li>/gis; |
734
|
0
|
|
|
|
|
0
|
$body =~ s/<\/li>\s+<\/ul>/<\/li><\/ul>/gis; |
735
|
0
|
|
|
|
|
0
|
$body =~ s/<ul>\s+<li>/<ul><li>/gis; |
736
|
0
|
|
|
|
|
0
|
$body =~ s/\s+<\/li>/<\/li>/gis; |
737
|
0
|
|
|
|
|
0
|
$body =~ s/\<\/option\>\s+\<option/\<\/option\>\<option/gis; |
738
|
0
|
|
|
|
|
0
|
$body =~ s/<title>\s*(.+?)\s*<\/title>/<title>$1<\/title>/is; |
739
|
0
|
|
|
|
|
0
|
$body =~ s/<\/center>\s+<center>/ /gis; |
740
|
|
|
|
|
|
|
} |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
# Create a key for the cache |
743
|
|
|
|
|
|
|
sub _generate_key { |
744
|
0
|
0
|
|
0
|
|
0
|
if($cache_key) { |
745
|
0
|
|
|
|
|
0
|
return $cache_key; |
746
|
|
|
|
|
|
|
} |
747
|
0
|
0
|
|
|
|
0
|
unless(defined($info)) { |
748
|
0
|
|
|
|
|
0
|
$info = CGI::Info->new({ cache => $cache }); |
749
|
|
|
|
|
|
|
} |
750
|
|
|
|
|
|
|
|
751
|
0
|
|
|
|
|
0
|
my $key = $info->browser_type() . '::' . $info->domain_name() . '::' . $info->script_name() . '::' . $info->as_string(); |
752
|
0
|
0
|
|
|
|
0
|
if($lingua) { |
753
|
0
|
|
|
|
|
0
|
$key .= '::' . $lingua->language(); |
754
|
|
|
|
|
|
|
} |
755
|
0
|
0
|
|
|
|
0
|
if($ENV{'HTTP_COOKIE'}) { |
756
|
|
|
|
|
|
|
# Different states of the client are stored in different caches |
757
|
|
|
|
|
|
|
# Don't put different Google Analytics in different caches, and anyway they |
758
|
|
|
|
|
|
|
# would be wrong |
759
|
0
|
|
|
|
|
0
|
foreach my $cookie(split(/;/, $ENV{'HTTP_COOKIE'})) { |
760
|
0
|
0
|
|
|
|
0
|
unless($cookie =~ /^__utm[abcz]/) { |
761
|
0
|
|
|
|
|
0
|
$key .= "::$cookie"; |
762
|
|
|
|
|
|
|
} |
763
|
|
|
|
|
|
|
} |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
# Honour the Vary headers |
767
|
0
|
0
|
0
|
|
|
0
|
if($headers && ($headers =~ /^Vary: .*$/m)) { |
768
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
769
|
0
|
|
|
|
|
0
|
$logger->debug('Found Vary header'); |
770
|
|
|
|
|
|
|
} |
771
|
0
|
|
|
|
|
0
|
foreach my $h1(split(/\r?\n/, $headers)) { |
772
|
0
|
|
|
|
|
0
|
my ($h1_name, $h1_value) = split /\:\s*/, $h1, 2; |
773
|
0
|
0
|
|
|
|
0
|
if(lc($h1_name) eq 'vary') { |
774
|
0
|
|
|
|
|
0
|
foreach my $h2(split(/\r?\n/, $headers)) { |
775
|
0
|
|
|
|
|
0
|
my ($h2_name, $h2_value) = split /\:\s*/, $h2, 2; |
776
|
0
|
0
|
|
|
|
0
|
if($h2_name eq $h1_value) { |
777
|
0
|
|
|
|
|
0
|
$key .= '::' . $h2_value; |
778
|
0
|
|
|
|
|
0
|
last; |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
} |
783
|
|
|
|
|
|
|
} |
784
|
0
|
|
|
|
|
0
|
$key =~ s/\//::/g; |
785
|
0
|
|
|
|
|
0
|
$key =~ s/::::/::/g; |
786
|
0
|
|
|
|
|
0
|
$key =~ s/::$//; |
787
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
788
|
0
|
|
|
|
|
0
|
$logger->trace("Returning $key"); |
789
|
|
|
|
|
|
|
} |
790
|
0
|
|
|
|
|
0
|
$cache_key = $key; |
791
|
0
|
|
|
|
|
0
|
return $key; |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
=head2 init |
795
|
|
|
|
|
|
|
|
796
|
|
|
|
|
|
|
Set various options and override default values. |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
# Put this toward the top of your program before you do anything |
799
|
|
|
|
|
|
|
# By default, generate_tag, generate_304 and compress_content are ON, |
800
|
|
|
|
|
|
|
# optimise_content and lint_content are OFF. Set optimise_content to 2 to |
801
|
|
|
|
|
|
|
# do aggressive JavaScript optimisations which may fail. |
802
|
|
|
|
|
|
|
use CGI::Buffer; |
803
|
|
|
|
|
|
|
CGI::Buffer::init( |
804
|
|
|
|
|
|
|
generate_etag => 1, # make good use of client's cache |
805
|
|
|
|
|
|
|
generate_last_modified => 1, # more use of client's cache |
806
|
|
|
|
|
|
|
compress_content => 1, # if gzip the output |
807
|
|
|
|
|
|
|
optimise_content => 0, # optimise your program's HTML, CSS and JavaScript |
808
|
|
|
|
|
|
|
cache => CHI->new(driver => 'File'), # cache requests |
809
|
|
|
|
|
|
|
cache_key => 'string', # key for the cache |
810
|
|
|
|
|
|
|
cache_age => '10 minutes', # how long to store responses in the cache |
811
|
|
|
|
|
|
|
logger => $logger, |
812
|
|
|
|
|
|
|
lint_content => 0, # Pass through HTML::Lint |
813
|
|
|
|
|
|
|
generate_304 => 1, # Generate 304: Not modified |
814
|
|
|
|
|
|
|
lingua => CGI::Lingua->new(), |
815
|
|
|
|
|
|
|
); |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
If no cache_key is given, one will be generated which may not be unique. |
818
|
|
|
|
|
|
|
The cache_key should be a unique value dependent upon the values set by the |
819
|
|
|
|
|
|
|
browser. |
820
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
The cache object will be an object that understands get_object(), |
822
|
|
|
|
|
|
|
set(), remove() and created_at() messages, such as an L<CHI> object. It is |
823
|
|
|
|
|
|
|
used as a server-side cache to reduce the need to rerun database accesses. |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
Items stay in the server-side cache by default for 10 minutes. |
826
|
|
|
|
|
|
|
This can be overridden by the cache_control HTTP header in the request, and |
827
|
|
|
|
|
|
|
the default can be changed by the cache_age argument to init(). |
828
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
Logger will be an object that understands debug() such as an L<Log::Log4perl> |
830
|
|
|
|
|
|
|
object. |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
To generate a last_modified header, you must give a cache object. |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
Init allows a reference of the options to be passed. So both of these work: |
835
|
|
|
|
|
|
|
use CGI::Buffer; |
836
|
|
|
|
|
|
|
#... |
837
|
|
|
|
|
|
|
CGI::Buffer::init(generate_etag => 1); |
838
|
|
|
|
|
|
|
CGI::Buffer::init({ generate_etag => 1, info => CGI::Info->new() }); |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
Generally speaking, passing by reference is better since it copies less on to |
841
|
|
|
|
|
|
|
the stack. |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
Alternatively you can give the options when loading the package: |
844
|
|
|
|
|
|
|
use CGI::Buffer { optimise_content => 1 }; |
845
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
=cut |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
sub init { |
849
|
0
|
0
|
|
0
|
1
|
0
|
my %params = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_; |
|
0
|
|
|
|
|
0
|
|
850
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
# Safe options - can be called at any time |
852
|
0
|
0
|
|
|
|
0
|
if(defined($params{generate_etag})) { |
853
|
0
|
|
|
|
|
0
|
$generate_etag = $params{generate_etag}; |
854
|
|
|
|
|
|
|
} |
855
|
0
|
0
|
|
|
|
0
|
if(defined($params{generate_last_modified})) { |
856
|
0
|
|
|
|
|
0
|
$generate_last_modified = $params{generate_last_modified}; |
857
|
|
|
|
|
|
|
} |
858
|
0
|
0
|
|
|
|
0
|
if(defined($params{compress_content})) { |
859
|
0
|
|
|
|
|
0
|
$compress_content = $params{compress_content}; |
860
|
|
|
|
|
|
|
} |
861
|
0
|
0
|
|
|
|
0
|
if(defined($params{optimise_content})) { |
862
|
0
|
|
|
|
|
0
|
$optimise_content = $params{optimise_content}; |
863
|
|
|
|
|
|
|
} |
864
|
0
|
0
|
|
|
|
0
|
if(defined($params{lint_content})) { |
865
|
0
|
|
|
|
|
0
|
$lint_content = $params{lint_content}; |
866
|
|
|
|
|
|
|
} |
867
|
0
|
0
|
|
|
|
0
|
if(defined($params{logger})) { |
868
|
0
|
|
|
|
|
0
|
$logger = $params{logger}; |
869
|
|
|
|
|
|
|
} |
870
|
0
|
0
|
|
|
|
0
|
if(defined($params{lingua})) { |
871
|
0
|
|
|
|
|
0
|
$lingua = $params{lingua}; |
872
|
|
|
|
|
|
|
} |
873
|
0
|
0
|
|
|
|
0
|
if(defined($params{generate_304})) { |
874
|
0
|
|
|
|
|
0
|
$generate_304 = $params{generate_304}; |
875
|
|
|
|
|
|
|
} |
876
|
0
|
0
|
0
|
|
|
0
|
if(defined($params{info}) && (!defined($info))) { |
877
|
0
|
|
|
|
|
0
|
$info = $params{info}; |
878
|
|
|
|
|
|
|
} |
879
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
# Unsafe options - must be called before output has been started |
881
|
0
|
|
|
|
|
0
|
my $pos = $CGI::Buffer::buf->getpos; |
882
|
0
|
0
|
|
|
|
0
|
if($pos > 0) { |
883
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
884
|
0
|
|
|
|
|
0
|
my @call_details = caller(0); |
885
|
0
|
|
|
|
|
0
|
$logger->warn("Too late to call init, $pos characters have been printed, caller line $call_details[2] of $call_details[1]"); |
886
|
|
|
|
|
|
|
} else { |
887
|
|
|
|
|
|
|
# Must do Carp::carp instead of carp for Test::Carp |
888
|
0
|
|
|
|
|
0
|
Carp::carp "Too late to call init, $pos characters have been printed"; |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
} |
891
|
0
|
0
|
0
|
|
|
0
|
if(defined($params{cache}) && can_cache()) { |
892
|
0
|
0
|
|
|
|
0
|
if(defined($ENV{'HTTP_CACHE_CONTROL'})) { |
893
|
0
|
|
|
|
|
0
|
my $control = $ENV{'HTTP_CACHE_CONTROL'}; |
894
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
895
|
0
|
|
|
|
|
0
|
$logger->debug("cache_control = $control"); |
896
|
|
|
|
|
|
|
} |
897
|
0
|
0
|
|
|
|
0
|
if($control =~ /^max-age\s*=\s*(\d+)$/) { |
898
|
|
|
|
|
|
|
# There is an argument not to do this |
899
|
|
|
|
|
|
|
# since one client will affect others |
900
|
0
|
|
|
|
|
0
|
$cache_age = "$1 seconds"; |
901
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
902
|
0
|
|
|
|
|
0
|
$logger->debug("cache_age = $cache_age"); |
903
|
|
|
|
|
|
|
} |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
} |
906
|
0
|
|
0
|
|
|
0
|
$cache_age ||= $params{cache_age}; |
907
|
|
|
|
|
|
|
|
908
|
0
|
0
|
0
|
|
|
0
|
if((!defined($params{cache})) && defined($cache)) { |
909
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
910
|
0
|
0
|
|
|
|
0
|
if($cache_key) { |
911
|
0
|
|
|
|
|
0
|
$logger->debug("disabling cache $cache_key"); |
912
|
|
|
|
|
|
|
} else { |
913
|
0
|
|
|
|
|
0
|
$logger->debug('disabling cache'); |
914
|
|
|
|
|
|
|
} |
915
|
|
|
|
|
|
|
} |
916
|
0
|
|
|
|
|
0
|
$cache = undef; |
917
|
|
|
|
|
|
|
} else { |
918
|
0
|
|
|
|
|
0
|
$cache = $params{cache}; |
919
|
|
|
|
|
|
|
} |
920
|
0
|
0
|
|
|
|
0
|
if(defined($params{cache_key})) { |
921
|
0
|
|
|
|
|
0
|
$cache_key = $params{cache_key}; |
922
|
|
|
|
|
|
|
} |
923
|
|
|
|
|
|
|
} |
924
|
|
|
|
|
|
|
} |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
sub import { |
927
|
|
|
|
|
|
|
# my $class = shift; |
928
|
6
|
|
|
6
|
|
89
|
shift; |
929
|
|
|
|
|
|
|
|
930
|
6
|
50
|
|
|
|
96
|
return unless @_; |
931
|
|
|
|
|
|
|
|
932
|
0
|
|
|
|
|
0
|
init(@_); |
933
|
|
|
|
|
|
|
} |
934
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
=head2 set_options |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
Synonym for init, kept for historical reasons. |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
=cut |
940
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
sub set_options { |
942
|
0
|
0
|
|
0
|
1
|
0
|
my %params = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_; |
|
0
|
|
|
|
|
0
|
|
943
|
|
|
|
|
|
|
|
944
|
0
|
|
|
|
|
0
|
init(\%params); |
945
|
|
|
|
|
|
|
} |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
=head2 can_cache |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
Returns true if the server is allowed to store the results locally. |
950
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
=cut |
952
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
sub can_cache { |
954
|
3
|
50
|
|
3
|
1
|
1264
|
if(defined($x_cache)) { |
955
|
0
|
|
|
|
|
0
|
return ($x_cache eq 'HIT'); |
956
|
|
|
|
|
|
|
} |
957
|
|
|
|
|
|
|
|
958
|
3
|
50
|
33
|
|
|
28
|
if(defined($ENV{'NO_CACHE'}) || defined($ENV{'NO_STORE'})) { |
959
|
0
|
|
|
|
|
0
|
$x_cache = 'MISS'; |
960
|
0
|
|
|
|
|
0
|
return 0; |
961
|
|
|
|
|
|
|
} |
962
|
3
|
50
|
|
|
|
16
|
if(defined($ENV{'HTTP_CACHE_CONTROL'})) { |
963
|
0
|
|
|
|
|
0
|
my $control = $ENV{'HTTP_CACHE_CONTROL'}; |
964
|
0
|
0
|
|
|
|
0
|
if(defined($logger)) { |
965
|
0
|
|
|
|
|
0
|
$logger->debug("cache_control = $control"); |
966
|
|
|
|
|
|
|
} |
967
|
|
|
|
|
|
|
# TODO: check Authorization header not present |
968
|
0
|
0
|
0
|
|
|
0
|
if(($control eq 'no-store') || |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
969
|
|
|
|
|
|
|
($control eq 'no-cache') || |
970
|
|
|
|
|
|
|
($control eq 'max-age=0') || |
971
|
|
|
|
|
|
|
($control eq 'private')) { |
972
|
0
|
|
|
|
|
0
|
$x_cache = 'MISS'; |
973
|
0
|
|
|
|
|
0
|
return 0; |
974
|
|
|
|
|
|
|
} |
975
|
|
|
|
|
|
|
} |
976
|
3
|
|
|
|
|
8
|
$x_cache = 'HIT'; |
977
|
3
|
|
|
|
|
15
|
return 1; |
978
|
|
|
|
|
|
|
} |
979
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
=head2 is_cached |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
Returns true if the output is cached. If it is then it means that all of the |
983
|
|
|
|
|
|
|
expensive routines in the CGI script can be by-passed because we already have |
984
|
|
|
|
|
|
|
the result stored in the cache. |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
# Put this toward the top of your program before you do anything |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
# Example key generation - use whatever you want as something |
989
|
|
|
|
|
|
|
# unique for this call, so that subsequent calls with the same |
990
|
|
|
|
|
|
|
# values match something in the cache |
991
|
|
|
|
|
|
|
use CGI::Info; |
992
|
|
|
|
|
|
|
use CGI::Lingua; |
993
|
|
|
|
|
|
|
use CGI::Buffer; |
994
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
my $i = CGI::Info->new(); |
996
|
|
|
|
|
|
|
my $l = CGI::Lingua->new(supported => ['en']); |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
# To use server side caching you must give the cache argument, however |
999
|
|
|
|
|
|
|
# the cache_key argument is optional - if you don't give one then one will |
1000
|
|
|
|
|
|
|
# be generated for you |
1001
|
|
|
|
|
|
|
if(CGI::Buffer::can_cache()) { |
1002
|
|
|
|
|
|
|
CGI::Buffer::init( |
1003
|
|
|
|
|
|
|
cache => CHI->new(driver => 'File'), |
1004
|
|
|
|
|
|
|
cache_key => $i->domain_name() . '/' . $i->script_name() . '/' . $i->as_string() . '/' . $l->language() |
1005
|
|
|
|
|
|
|
); |
1006
|
|
|
|
|
|
|
if(CGI::Buffer::is_cached()) { |
1007
|
|
|
|
|
|
|
# Output will be retrieved from the cache and sent automatically |
1008
|
|
|
|
|
|
|
exit; |
1009
|
|
|
|
|
|
|
} |
1010
|
|
|
|
|
|
|
} |
1011
|
|
|
|
|
|
|
# Not in the cache, so now do our expensive computing to generate the |
1012
|
|
|
|
|
|
|
# results |
1013
|
|
|
|
|
|
|
print "Content-type: text/html\n"; |
1014
|
|
|
|
|
|
|
# ... |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
=cut |
1017
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
sub is_cached { |
1019
|
9
|
50
|
|
9
|
1
|
215
|
unless($cache) { |
1020
|
9
|
50
|
|
|
|
38
|
if($logger) { |
1021
|
0
|
|
|
|
|
0
|
$logger->debug("is_cached: cache hasn't been enabled"); |
1022
|
|
|
|
|
|
|
} |
1023
|
9
|
|
|
|
|
48
|
return 0; |
1024
|
|
|
|
|
|
|
} |
1025
|
|
|
|
|
|
|
|
1026
|
0
|
|
|
|
|
|
my $key = _generate_key(); |
1027
|
|
|
|
|
|
|
|
1028
|
0
|
0
|
|
|
|
|
if($logger) { |
1029
|
0
|
|
|
|
|
|
$logger->debug("is_cached: looking for key = $key"); |
1030
|
|
|
|
|
|
|
} |
1031
|
0
|
|
|
|
|
|
$cobject = $cache->get_object($key); |
1032
|
0
|
0
|
|
|
|
|
unless($cobject) { |
1033
|
0
|
0
|
|
|
|
|
if($logger) { |
1034
|
0
|
|
|
|
|
|
$logger->debug('not found in cache'); |
1035
|
|
|
|
|
|
|
} |
1036
|
0
|
|
|
|
|
|
return 0; |
1037
|
|
|
|
|
|
|
} |
1038
|
0
|
0
|
|
|
|
|
unless($cobject->value($key)) { |
1039
|
0
|
0
|
|
|
|
|
if($logger) { |
1040
|
0
|
|
|
|
|
|
$logger->warn('is_cached: object is in the cache but not the data'); |
1041
|
|
|
|
|
|
|
} |
1042
|
0
|
|
|
|
|
|
$cobject = undef; |
1043
|
0
|
|
|
|
|
|
return 0; |
1044
|
|
|
|
|
|
|
} |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
# If the script has changed, don't use the cache since we may produce |
1047
|
|
|
|
|
|
|
# different output |
1048
|
0
|
|
|
|
|
|
my $age = _my_age(); |
1049
|
0
|
0
|
|
|
|
|
unless(defined($age)) { |
1050
|
0
|
0
|
|
|
|
|
if($logger) { |
1051
|
0
|
|
|
|
|
|
$logger->debug("Can't determine script's age"); |
1052
|
|
|
|
|
|
|
} |
1053
|
|
|
|
|
|
|
# Can't determine the age. Play it safe an assume we're not |
1054
|
|
|
|
|
|
|
# cached |
1055
|
0
|
|
|
|
|
|
$cobject = undef; |
1056
|
0
|
|
|
|
|
|
return 0; |
1057
|
|
|
|
|
|
|
} |
1058
|
0
|
0
|
|
|
|
|
if($age > $cobject->created_at()) { |
1059
|
|
|
|
|
|
|
# Script has been updated so it may produce different output |
1060
|
0
|
0
|
|
|
|
|
if($logger) { |
1061
|
0
|
|
|
|
|
|
$logger->debug('Script has been updated'); |
1062
|
|
|
|
|
|
|
} |
1063
|
0
|
|
|
|
|
|
$cobject = undef; |
1064
|
|
|
|
|
|
|
# Nothing will be in date and all new searches would miss |
1065
|
|
|
|
|
|
|
# anyway, so may as well clear it all |
1066
|
|
|
|
|
|
|
# FIXME: RT104471 |
1067
|
|
|
|
|
|
|
# $cache->clear(); |
1068
|
0
|
|
|
|
|
|
return 0; |
1069
|
|
|
|
|
|
|
} |
1070
|
0
|
0
|
|
|
|
|
if($logger) { |
1071
|
0
|
|
|
|
|
|
$logger->debug('Script is in the cache'); |
1072
|
|
|
|
|
|
|
} |
1073
|
0
|
|
|
|
|
|
return 1; |
1074
|
|
|
|
|
|
|
} |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
sub _my_age { |
1077
|
0
|
0
|
|
0
|
|
|
if($script_mtime) { |
1078
|
0
|
|
|
|
|
|
return $script_mtime; |
1079
|
|
|
|
|
|
|
} |
1080
|
0
|
0
|
|
|
|
|
unless(defined($info)) { |
1081
|
0
|
0
|
|
|
|
|
if($cache) { |
1082
|
0
|
|
|
|
|
|
$info = CGI::Info->new({ cache => $cache }); |
1083
|
|
|
|
|
|
|
} else { |
1084
|
0
|
|
|
|
|
|
$info = CGI::Info->new(); |
1085
|
|
|
|
|
|
|
} |
1086
|
|
|
|
|
|
|
} |
1087
|
|
|
|
|
|
|
|
1088
|
0
|
|
|
|
|
|
my $path = $info->script_path(); |
1089
|
0
|
0
|
|
|
|
|
unless(defined($path)) { |
1090
|
0
|
|
|
|
|
|
return; |
1091
|
|
|
|
|
|
|
} |
1092
|
|
|
|
|
|
|
|
1093
|
0
|
|
|
|
|
|
my @statb = stat($path); |
1094
|
0
|
|
|
|
|
|
$script_mtime = $statb[9]; |
1095
|
0
|
|
|
|
|
|
return $script_mtime; |
1096
|
|
|
|
|
|
|
} |
1097
|
|
|
|
|
|
|
|
1098
|
|
|
|
|
|
|
sub _should_gzip |
1099
|
|
|
|
|
|
|
{ |
1100
|
0
|
0
|
0
|
0
|
|
|
if($compress_content && ($ENV{'HTTP_ACCEPT_ENCODING'} || $ENV{'HTTP_TE'})) { |
|
|
|
0
|
|
|
|
|
1101
|
0
|
0
|
|
|
|
|
if(scalar(@content_type)) { |
1102
|
0
|
0
|
|
|
|
|
if($content_type[0] ne 'text') { |
1103
|
0
|
|
|
|
|
|
return ''; |
1104
|
|
|
|
|
|
|
} |
1105
|
|
|
|
|
|
|
} |
1106
|
0
|
0
|
|
|
|
|
my $accept = lc($ENV{'HTTP_ACCEPT_ENCODING'} ? $ENV{'HTTP_ACCEPT_ENCODING'} : $ENV{'HTTP_TE'}); |
1107
|
0
|
|
|
|
|
|
foreach my $method(split(/,\s?/, $accept)) { |
1108
|
0
|
0
|
0
|
|
|
|
if(($method eq 'gzip') || ($method eq 'x-gzip') || ($method eq 'br')) { |
|
|
|
0
|
|
|
|
|
1109
|
0
|
|
|
|
|
|
return $method; |
1110
|
|
|
|
|
|
|
} |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
0
|
|
|
|
|
|
return ''; |
1115
|
|
|
|
|
|
|
} |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
sub _set_content_type |
1118
|
|
|
|
|
|
|
{ |
1119
|
0
|
|
|
0
|
|
|
my $headers = shift; |
1120
|
|
|
|
|
|
|
|
1121
|
0
|
|
|
|
|
|
foreach my $header (split(/\r?\n/, $headers)) { |
1122
|
0
|
|
|
|
|
|
my ($header_name, $header_value) = split /\:\s*/, $header, 2; |
1123
|
0
|
0
|
|
|
|
|
if (lc($header_name) eq 'content-type') { |
1124
|
0
|
|
|
|
|
|
@content_type = split /\//, $header_value, 2; |
1125
|
0
|
|
|
|
|
|
last; |
1126
|
|
|
|
|
|
|
} |
1127
|
|
|
|
|
|
|
} |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
sub _compress { |
1131
|
0
|
0
|
|
0
|
|
|
my %params = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_; |
|
0
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
|
1133
|
0
|
|
|
|
|
|
my $encoding = $params{encoding}; |
1134
|
|
|
|
|
|
|
|
1135
|
0
|
0
|
0
|
|
|
|
if((length($encoding) == 0) || (length($body) < MIN_GZIP_LEN)) { |
1136
|
0
|
|
|
|
|
|
return; |
1137
|
|
|
|
|
|
|
} |
1138
|
|
|
|
|
|
|
|
1139
|
0
|
0
|
|
|
|
|
if($encoding eq 'gzip') { |
|
|
0
|
|
|
|
|
|
1140
|
0
|
|
|
|
|
|
require Compress::Zlib; |
1141
|
0
|
|
|
|
|
|
Compress::Zlib->import; |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
# Avoid 'Wide character in memGzip' |
1144
|
0
|
0
|
|
|
|
|
unless($encode_loaded) { |
1145
|
0
|
|
|
|
|
|
require Encode; |
1146
|
0
|
|
|
|
|
|
$encode_loaded = 1; |
1147
|
|
|
|
|
|
|
} |
1148
|
0
|
|
|
|
|
|
my $nbody = Compress::Zlib::memGzip(\Encode::encode_utf8($body)); |
1149
|
0
|
0
|
|
|
|
|
if(length($nbody) < length($body)) { |
1150
|
0
|
|
|
|
|
|
$body = $nbody; |
1151
|
0
|
|
|
|
|
|
push @o, "Content-Encoding: $encoding"; |
1152
|
0
|
|
|
|
|
|
push @o, "Vary: Accept-Encoding"; |
1153
|
|
|
|
|
|
|
} |
1154
|
|
|
|
|
|
|
} elsif($encoding eq 'br') { |
1155
|
0
|
|
|
|
|
|
require IO::Compress::Brotli; |
1156
|
0
|
|
|
|
|
|
IO::Compress::Brotli->import(); |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
# Avoid 'Wide character in memGzip' |
1159
|
0
|
0
|
|
|
|
|
unless($encode_loaded) { |
1160
|
0
|
|
|
|
|
|
require Encode; |
1161
|
0
|
|
|
|
|
|
$encode_loaded = 1; |
1162
|
|
|
|
|
|
|
} |
1163
|
0
|
|
|
|
|
|
my $nbody = IO::Compress::Brotli::bro(Encode::encode_utf8($body)); |
1164
|
0
|
0
|
|
|
|
|
if(length($nbody) < length($body)) { |
1165
|
0
|
|
|
|
|
|
$body = $nbody; |
1166
|
0
|
|
|
|
|
|
push @o, "Content-Encoding: $encoding"; |
1167
|
0
|
|
|
|
|
|
push @o, "Vary: Accept-Encoding"; |
1168
|
|
|
|
|
|
|
} |
1169
|
|
|
|
|
|
|
} |
1170
|
|
|
|
|
|
|
} |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
=head1 AUTHOR |
1173
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
Nigel Horne, C<< <njh at bandsman.co.uk> >> |
1175
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
=head1 BUGS |
1177
|
|
|
|
|
|
|
|
1178
|
|
|
|
|
|
|
CGI::Buffer should be safe even in scripts which produce lots of different |
1179
|
|
|
|
|
|
|
output, e.g. e-commerce situations. |
1180
|
|
|
|
|
|
|
On such pages, however, I strongly urge to setting generate_304 to 0 and |
1181
|
|
|
|
|
|
|
sending the HTTP header "Cache-Control: no-cache". |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
When using L<Template>, ensure that you don't use it to output to STDOUT, |
1184
|
|
|
|
|
|
|
instead you will need to capture into a variable and print that. |
1185
|
|
|
|
|
|
|
For example: |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
my $output; |
1188
|
|
|
|
|
|
|
$template->process($input, $vars, \$output) || ($output = $template->error()); |
1189
|
|
|
|
|
|
|
print $output; |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
Can produce buggy JavaScript if you use the <!-- HIDING technique. |
1192
|
|
|
|
|
|
|
This is a bug in L<JavaScript::Packer>, not CGI::Buffer. |
1193
|
|
|
|
|
|
|
See https://github.com/nevesenin/javascript-packer-perl/issues/1#issuecomment-4356790 |
1194
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
Mod_deflate can confuse this when compressing output. |
1196
|
|
|
|
|
|
|
Ensure that deflation is off for .pl files: |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|pl)$ no-gzip dont-vary |
1199
|
|
|
|
|
|
|
|
1200
|
|
|
|
|
|
|
If you request compressed output then uncompressed output (or vice |
1201
|
|
|
|
|
|
|
versa) on input that produces the same output, the status will be 304. |
1202
|
|
|
|
|
|
|
The letter of the spec says that's wrong, so I'm noting it here, but |
1203
|
|
|
|
|
|
|
in practice you should not see this happen or have any difficulties |
1204
|
|
|
|
|
|
|
because of it. |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
CGI::Buffer is not compatible with FastCGI. |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
I advise adding CGI::Buffer as the last use statement so that it is |
1209
|
|
|
|
|
|
|
cleared up first. In particular it should be loaded after |
1210
|
|
|
|
|
|
|
L<Log::Log4perl>, if you're using that, so that any messages it |
1211
|
|
|
|
|
|
|
produces are printed after the HTTP headers have been sent by |
1212
|
|
|
|
|
|
|
CGI::Buffer; |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
CGI::Buffer is not compatible with FCGI, use L<FCGI::Buffer> instead. |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
Please report any bugs or feature requests to C<bug-cgi-buffer at rt.cpan.org>, |
1217
|
|
|
|
|
|
|
or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=CGI-Buffer>. |
1218
|
|
|
|
|
|
|
I will be notified, and then you'll automatically be notified of progress on |
1219
|
|
|
|
|
|
|
your bug as I make changes. |
1220
|
|
|
|
|
|
|
|
1221
|
|
|
|
|
|
|
=head1 SEE ALSO |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
L<HTML::Packer>, L<HTML::Lint> |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
=head1 SUPPORT |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
You can find documentation for this module with the perldoc command. |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
perldoc CGI::Buffer |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
You can also look for information at: |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
=over 4 |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
=item * MetaCPAN |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
L<https://metacpan.org/release/CGI-Buffer> |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
=item * RT: CPAN's request tracker |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=CGI-Buffer> |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
=item * CPANTS |
1244
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
L<http://cpants.cpanauthors.org/dist/CGI-Buffer> |
1246
|
|
|
|
|
|
|
|
1247
|
|
|
|
|
|
|
=item * CPAN Testers' Matrix |
1248
|
|
|
|
|
|
|
|
1249
|
|
|
|
|
|
|
L<http://matrix.cpantesters.org/?dist=CGI-Buffer> |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
=item * CPAN Ratings |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
L<http://cpanratings.perl.org/d/CGI-Buffer> |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
=item * CPAN Testers Dependencies |
1256
|
|
|
|
|
|
|
|
1257
|
|
|
|
|
|
|
L<http://deps.cpantesters.org/?module=CGI::Buffer> |
1258
|
|
|
|
|
|
|
|
1259
|
|
|
|
|
|
|
=back |
1260
|
|
|
|
|
|
|
|
1261
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
1262
|
|
|
|
|
|
|
|
1263
|
|
|
|
|
|
|
The inspiration and code for some of this is cgi_buffer by Mark |
1264
|
|
|
|
|
|
|
Nottingham: L<https://www.mnot.net/blog/2003/04/24/etags>. |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
1267
|
|
|
|
|
|
|
|
1268
|
|
|
|
|
|
|
The licence for cgi_buffer is: |
1269
|
|
|
|
|
|
|
|
1270
|
|
|
|
|
|
|
"(c) 2000 Copyright Mark Nottingham <mnot@pobox.com> |
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
This software may be freely distributed, modified and used, |
1273
|
|
|
|
|
|
|
provided that this copyright notice remain intact. |
1274
|
|
|
|
|
|
|
|
1275
|
|
|
|
|
|
|
This software is provided 'as is' without warranty of any kind." |
1276
|
|
|
|
|
|
|
|
1277
|
|
|
|
|
|
|
The rest of the program is Copyright 2011-2023 Nigel Horne, |
1278
|
|
|
|
|
|
|
and is released under the following licence: GPL2 |
1279
|
|
|
|
|
|
|
|
1280
|
|
|
|
|
|
|
=cut |
1281
|
|
|
|
|
|
|
|
1282
|
|
|
|
|
|
|
1; # End of CGI::Buffer |