File Coverage

blib/lib/CGI/Application/Plugin/Session.pm
Criterion Covered Total %
statement 90 134 67.1
branch 55 78 70.5
condition 20 49 40.8
subroutine 13 16 81.2
pod 6 6 100.0
total 184 283 65.0


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