From 4f5365ee3c79d712089e5fedef7c8cddd713a149 Mon Sep 17 00:00:00 2001 From: Galen Charlton Date: Sun, 20 Oct 2013 17:13:22 +0000 Subject: [PATCH] Bug 10016: force zero browser-side caching of SCO pages This patch makes the web-based self-check module pages specify that no browser (or proxy caching) occur at all. This prevents a security issue where letting the SCO session time out, then hitting the back button allowed one to view the previous patron's session. This patch adds an optional fifth parameter to output_with_http_headers(), and output_html_with_http_headers(), a hashref for miscellaneous options. One key is defined at the moment: force_no_caching, which if if present and set to a true value, sets HTTP headers to specify no browser caching of the page at all. To test: [1] Start a web-based self-check session and optionally perform some transactions. [2] Allow the session to time out (it may be helpful to set SelfCheckTimeout to a low value such as 10 seconds). [3] Hit the back button. You should not see the previous patron's self-check session. [4] Verify that prove -v t/Output.t passes. Signed-off-by: Galen Charlton Signed-off-by: Ed Veal --- C4/Output.pm | 20 +++++++++++++++----- opac/sco/sco-main.pl | 2 +- t/Output.t | 22 +++++++++++++++++++++- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/C4/Output.pm b/C4/Output.pm index 6e2c89a..324cf69 100644 --- a/C4/Output.pm +++ b/C4/Output.pm @@ -255,7 +255,7 @@ sub pagination_bar { =item output_with_http_headers - &output_with_http_headers($query, $cookie, $data, $content_type[, $status]) + &output_with_http_headers($query, $cookie, $data, $content_type[, $status[, $extra_options]]) Outputs $data with the appropriate HTTP headers, the authentication cookie $cookie and a Content-Type specified in @@ -267,12 +267,18 @@ $content_type is one of the following: 'html', 'js', 'json', 'xml', 'rss', or 'a $status is an HTTP status message, like '403 Authentication Required'. It defaults to '200 OK'. +$extra_options is hashref. If the key 'force_no_caching' is present and has +a true value, the HTTP headers include directives to force there to be no +caching whatsoever. + =cut sub output_with_http_headers { - my ( $query, $cookie, $data, $content_type, $status ) = @_; + my ( $query, $cookie, $data, $content_type, $status, $extra_options ) = @_; $status ||= '200 OK'; + $extra_options //= {}; + my %content_type_map = ( 'html' => 'text/html', 'js' => 'text/javascript', @@ -285,13 +291,17 @@ sub output_with_http_headers { ); die "Unknown content type '$content_type'" if ( !defined( $content_type_map{$content_type} ) ); + my $cache_policy = 'no-cache'; + $cache_policy .= ', no-store, max-age=0' if $extra_options->{force_no_caching}; my $options = { type => $content_type_map{$content_type}, status => $status, charset => 'UTF-8', Pragma => 'no-cache', - 'Cache-Control' => 'no-cache', + 'Cache-Control' => $cache_policy, }; + $options->{expires} = 'now' if $extra_options->{force_no_caching}; + $options->{cookie} = $cookie if $cookie; if ($content_type eq 'html') { # guaranteed to be one of the content_type_map keys, else we'd have died $options->{'Content-Style-Type' } = 'text/css'; @@ -308,8 +318,8 @@ sub output_with_http_headers { } sub output_html_with_http_headers { - my ( $query, $cookie, $data, $status ) = @_; - output_with_http_headers( $query, $cookie, $data, 'html', $status ); + my ( $query, $cookie, $data, $status, $extra_options ) = @_; + output_with_http_headers( $query, $cookie, $data, 'html', $status, $extra_options ); } diff --git a/opac/sco/sco-main.pl b/opac/sco/sco-main.pl index 62884ee..d1234a0 100755 --- a/opac/sco/sco-main.pl +++ b/opac/sco/sco-main.pl @@ -268,4 +268,4 @@ $template->param( SCOUserCSS => C4::Context->preference('SCOUserCSS'), ); -output_html_with_http_headers $query, $cookie, $template->output; +output_html_with_http_headers $query, $cookie, $template->output, undef, { force_no_caching => 1 }; diff --git a/t/Output.t b/t/Output.t index a70e7c8..24c1344 100755 --- a/t/Output.t +++ b/t/Output.t @@ -3,8 +3,28 @@ use strict; use warnings; -use Test::More tests => 1; +use Test::More tests => 5; +use CGI; BEGIN { use_ok('C4::Output'); } + +my $query = CGI->new(); +my $cookie; +my $output = 'foobarbaz'; + +{ + local *STDOUT; + my $stdout; + open STDOUT, '>', \$stdout; + output_html_with_http_headers $query, $cookie, $output, undef, { force_no_caching => 1 }; + like($stdout, qr/Cache-control: no-cache, no-store, max-age=0/, 'force_no_caching sets Cache-control as desired'); + like($stdout, qr/Expires: /, 'force_no_caching sets an Expires header'); + $stdout = ''; + close STDOUT; + open STDOUT, '>', \$stdout; + output_html_with_http_headers $query, $cookie, $output, undef, undef; + like($stdout, qr/Cache-control: no-cache[^,]/, 'not using force_no_caching sets Cache-control as desired'); + unlike($stdout, qr/Expires: /, 'force_no_caching does not set an Expires header'); +} -- 1.7.2.5