File Coverage

blib/lib/CGI/Buffer.pm
Criterion Covered Total %
statement 54 553 9.7
branch 12 366 3.2
condition 4 216 1.8
subroutine 15 24 62.5
pod 4 4 100.0
total 89 1163 7.6


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