File Coverage

blib/lib/CGI/Application/Plugin/Session.pm
Criterion Covered Total %
statement 111 134 82.8
branch 67 78 85.9
condition 30 49 61.2
subroutine 13 16 81.2
pod 6 6 100.0
total 227 283 80.2


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::Session;
2             $CGI::Application::Plugin::Session::VERSION = '1.06';
3 13     13   1993594 use CGI::Session ();
  13         90804  
  13         420  
4 13     13   114 use File::Spec ();
  13         26  
  13         400  
5 13     13   9730 use CGI::Application 3.21;
  13         116007  
  13         644  
6 13     13   141 use Carp qw(croak);
  13         37  
  13         881  
7 13     13   88 use Scalar::Util ();
  13         33  
  13         295  
8              
9             # ABSTRACT: Plugin that adds session support to CGI::Application
10              
11 13     13   61 use strict;
  13         43  
  13         340  
12 13     13   62 use vars qw($VERSION @EXPORT);
  13         24  
  13         31311  
13              
14             require Exporter;
15              
16             @EXPORT = qw(
17             session
18             session_config
19             session_cookie
20             session_delete
21             session_loaded
22             session_recreate
23             );
24 24     24   564304 sub import { goto &Exporter::import }
25              
26             sub session {
27 104     104 1 216829 my $self = shift;
28              
29 104 100       289 if (!$self->{__CAP__SESSION_OBJ}) {
30             # define the config hash if it doesn't exist to save some checks later
31 29 100       198 $self->{__CAP__SESSION_CONFIG} = {} unless $self->{__CAP__SESSION_CONFIG};
32              
33             # gather parameters for the CGI::Session module from the user,
34             # or use some sane defaults
35             my @params = ($self->{__CAP__SESSION_CONFIG}->{CGI_SESSION_OPTIONS}) ?
36 29 100       132 @{ $self->{__CAP__SESSION_CONFIG}->{CGI_SESSION_OPTIONS} } :
  28         110  
37             ('driver:File', $self->query, {Directory=>File::Spec->tmpdir});
38              
39              
40             # CGI::Session only works properly with CGI.pm so extract the sid manually if
41             # another module is being used
42 29 50 66     492 if (Scalar::Util::blessed($params[1]) && ! $params[1]->isa('CGI')) {
43 0         0 my $name = __locate_session_name( $self ); ## plugin method call
44 0   0     0 + my $sid = $params[1]->cookie($name) || $params[1]->param($name);
45 0         0 $params[1] = $sid;
46             }
47              
48             # create CGI::Session object or die with an error
49 29         266 $self->{__CAP__SESSION_OBJ} = CGI::Session->new(@params);
50 29 100       741368 if (! $self->{__CAP__SESSION_OBJ} ) {
51 1   50     4 my $errstr = CGI::Session->errstr || 'Unknown';
52 1         45 croak "Failed to Create CGI::Session object :: Reason: $errstr";
53             }
54              
55             # Set the default expiry if requested and if this is a new session
56 28 100 100     190 if ($self->{__CAP__SESSION_CONFIG}->{DEFAULT_EXPIRY} && $self->{__CAP__SESSION_OBJ}->is_new) {
57 11         162 $self->{__CAP__SESSION_OBJ}->expire($self->{__CAP__SESSION_CONFIG}->{DEFAULT_EXPIRY});
58             }
59              
60             # add the cookie to the outgoing headers under the following conditions
61             # if the cookie doesn't exist,
62             # or if the session ID doesn't match what is in the current cookie,
63             # or if the session has an expiry set on it
64             # but don't send it if SEND_COOKIE is set to 0
65 28 100 100     2439 if (!defined $self->{__CAP__SESSION_CONFIG}->{SEND_COOKIE} || $self->{__CAP__SESSION_CONFIG}->{SEND_COOKIE}) {
66             my $cid = $self->query->cookie(
67             $self->{__CAP__SESSION_OBJ}->name
68 26         179 );
69 26 100 100     10354 if (!$cid || $cid ne $self->{__CAP__SESSION_OBJ}->id || $self->{__CAP__SESSION_OBJ}->expire()) {
      100        
70 23         226 session_cookie($self);
71             }
72             }
73             }
74              
75 103         468 return $self->{__CAP__SESSION_OBJ};
76             }
77              
78             sub session_config {
79 34     34 1 3656530 my $self = shift;
80              
81 34 100       159 if (@_) {
82 33 100       160 die "Calling session_config after the session has already been created" if (defined $self->{__CAP__SESSION_OBJ});
83 31         63 my $props;
84 31 100       116 if (ref($_[0]) eq 'HASH') {
85 10         41 $props = $self->_cap_hash($_[0]);
86             } else {
87 21         127 $props = $self->_cap_hash({ @_ });
88             }
89              
90             # Check for CGI_SESSION_OPTIONS
91 31 100       911 if ($props->{CGI_SESSION_OPTIONS}) {
92 29 100       141 die "session_config error: parameter CGI_SESSION_OPTIONS is not an array reference" if ref $props->{CGI_SESSION_OPTIONS} ne 'ARRAY';
93 28         109 $self->{__CAP__SESSION_CONFIG}->{CGI_SESSION_OPTIONS} = delete $props->{CGI_SESSION_OPTIONS};
94             }
95              
96             # Check for COOKIE_PARAMS
97 30 100       104 if ($props->{COOKIE_PARAMS}) {
98 12 100       2033 die "session_config error: parameter COOKIE_PARAMS is not a hash reference" if ref $props->{COOKIE_PARAMS} ne 'HASH';
99 11         34 $self->{__CAP__SESSION_CONFIG}->{COOKIE_PARAMS} = delete $props->{COOKIE_PARAMS};
100             }
101              
102             # Check for SEND_COOKIE
103 29 100       117 if (defined $props->{SEND_COOKIE}) {
104 12 100       45 $self->{__CAP__SESSION_CONFIG}->{SEND_COOKIE} = (delete $props->{SEND_COOKIE}) ? 1 : 0;
105             }
106              
107             # Check for DEFAULT_EXPIRY
108 29 100       111 if (defined $props->{DEFAULT_EXPIRY}) {
109 13         38 $self->{__CAP__SESSION_CONFIG}->{DEFAULT_EXPIRY} = delete $props->{DEFAULT_EXPIRY};
110             }
111              
112             # If there are still entries left in $props then they are invalid
113 29 100       177 die "Invalid option(s) (".join(', ', keys %$props).") passed to session_config" if %$props;
114             }
115              
116 29         128 $self->{__CAP__SESSION_CONFIG};
117             }
118              
119             sub session_cookie {
120 24     24 1 368 my $self = shift;
121 24         73 my %options = @_;
122              
123             # merge in any parameters set by config_session
124 24 100       124 if ($self->{__CAP__SESSION_CONFIG}->{COOKIE_PARAMS}) {
125 11         23 %options = (%{ $self->{__CAP__SESSION_CONFIG}->{COOKIE_PARAMS} }, %options);
  11         103  
126             }
127              
128 24 100       107 if (!$self->{__CAP__SESSION_OBJ}) {
129             # The session object has not been created yet, so make sure we at least call it once
130 1         5 my $tmp = $self->session;
131             }
132              
133             ## check cookie option -name with session name
134             ## if different these may cause problems/confusion
135 24 100 100     176 if ( exists $options{'-name'} and
136             $options{'-name'} ne $self->session->name ) {
137             warn sprintf( "Cookie '%s' and Session '%s' name don't match.\n",
138 1         16 $options{'-name'}, $self->session->name )
139             }
140              
141             ## setup the values for cookie
142 24   66     278 $options{'-name'} ||= $self->session->name;
143 24   66     298 $options{'-value'} ||= $self->session->id;
144 24 100 100     219 if(defined($self->session->expires()) && !defined($options{'-expires'})) {
145 5         62 $options{'-expires'} = _build_exp_time( $self->session->expires() );
146             }
147 24         255 my $cookie = $self->query->cookie(%options);
148              
149             # Look for a cookie header in the existing headers
150 24         21065 my %headers = $self->header_props;
151 24         710 my $cookie_set = 0;
152 24 100       113 if (my $cookies = $headers{'-cookie'}) {
153 4 100       559 if (ref($cookies) eq 'ARRAY') {
    100          
154             # multiple cookie headers so check them all
155 2         10 for (my $i=0; $i < @$cookies; $i++) {
156             # replace the cookie inline if we find a match
157 4 100       84 if (substr($cookies->[$i], 0, length($options{'-name'})) eq $options{'-name'}) {
158 1         150 $cookies->[$i] = $cookie;
159 1         5 $cookie_set++;
160             }
161             }
162             } elsif (substr($cookies, 0, length($options{'-name'})) eq $options{'-name'}) {
163             # only one cookie and it is ours, so overwrite it
164 1         118 $self->header_add(-cookie => $cookie);
165 1         67 $cookie_set++;
166             }
167             }
168              
169 24 100       415 $self->header_add(-cookie => [$cookie]) unless $cookie_set;
170              
171 24         1409 return 1;
172             }
173              
174             sub _build_exp_time {
175 5     5   36 my $secs_until_expiry = shift;
176 5 50       19 return unless defined $secs_until_expiry;
177              
178             # Add a plus sign unless the number is negative
179 5 100       37 my $prefix = ($secs_until_expiry >= 0) ? '+' : '';
180              
181             # Add an 's' for "seconds".
182 5         25 return $prefix.$secs_until_expiry.'s';
183             }
184              
185             sub session_delete {
186 3     3 1 3168 my $self = shift;
187              
188 3 50       9 if ( my $session = $self->session ) {
189 3         10 $session->delete;
190 3         23 $session->flush;
191 3 50       216 if ( $self->{'__CAP__SESSION_CONFIG'}->{'SEND_COOKIE'} ) {
192 3         5 my %options;
193 3 50       9 if ( $self->{'__CAP__SESSION_CONFIG'}->{'COOKIE_PARAMS'} ) {
194 3         4 %options = ( %{ $self->{'__CAP__SESSION_CONFIG'}->{'COOKIE_PARAMS'} }, %options );
  3         13  
195             }
196 3   33     15 $options{'name'} ||= $session->name;
197 3         23 $options{'value'} = '';
198 3         29 $options{'-expires'} = '-1d';
199 3         9 my $newcookie = $self->query->cookie(\%options);
200              
201             # See if a session cookie has already been set (this will happen if
202             # this is a new session). We keep all existing cookies except the
203             # session cookie, which we replace with the timed out session
204             # cookie
205 3         838 my @keep;
206 3         7 my %headers = $self->header_props;
207 3   50     67 my $cookies = $headers{'-cookie'} || [];
208 3 100       9 $cookies = [$cookies] unless ref $cookies eq 'ARRAY';
209 3         6 foreach my $cookie (@$cookies) {
210 5 100 100     15 if ( ref($cookie) ne 'CGI::Cookie' || $cookie->name ne $session->name ) {
211             # keep this cookie
212 3         15 push @keep, $cookie;
213             }
214             }
215 3         25 push @keep, $newcookie;
216              
217             # We have to set the cookies this way, because CGI::Application has
218             # an annoying interface to the headers (why can't we have
219             # 'header_set as well as header_add?). The first call replaces all
220             # cookie headers with the one new cookie header, and the next call
221             # adds in the rest of the cookies if there are any.
222 3         10 $self->header_add( -cookie => shift @keep );
223 3 100       100 $self->header_add( -cookie => \@keep ) if @keep;
224             }
225             }
226             }
227              
228             sub session_loaded {
229 0     0 1   my $self = shift;
230 0           return defined $self->{__CAP__SESSION_OBJ};
231             }
232              
233             sub session_recreate {
234 0     0 1   my $self = shift;
235 0           my $data = {};
236              
237             # Copy all values from existing session and delete it
238 0 0         if (session_loaded($self)) {
239 0           $data = $self->session->param_hashref;
240 0           $self->session->delete;
241 0           $self->session->flush;
242 0           $self->{__CAP__SESSION_OBJ} = undef;
243              
244             }
245              
246             # create a new session and populate it
247             # (This should also send out a new cookie if so configured)
248 0           my $session = $self->session;
249 0           while(my($k,$v) = each %$data) {
250 0 0         next if index($k, '_SESSION_') == 0;
251 0           $session->param($k => $v);
252             }
253 0           $session->flush;
254              
255 0           return 1;
256             }
257              
258             ## all a hack to adjust for problems with cgi::session and
259             ## it not playing with non-CGI.pm objects
260             sub __locate_session_name {
261 0     0     my $self = shift;
262 0           my $sess_opts = $self->{__CAP__SESSION_CONFIG}->{CGI_SESSION_OPTIONS};
263              
264             ## search for 'name' cgi session option
265 0 0 0       if ( $sess_opts and $sess_opts->[4]
      0        
      0        
266             and ref $sess_opts->[4] eq 'HASH'
267             and exists $sess_opts->[4]->{name} ) {
268 0           return $sess_opts->[4]->{name};
269             }
270              
271 0           return CGI::Session->name;
272             }
273              
274             1;
275              
276             __END__