File Coverage

blib/lib/CGI/Buffer.pm
Criterion Covered Total %
statement 60 560 10.7
branch 14 372 3.7
condition 4 219 1.8
subroutine 15 24 62.5
pod 4 4 100.0
total 97 1179 8.2


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