File Coverage

lib/CGI/Mungo/Session.pm
Criterion Covered Total %
statement 101 179 56.4
branch 13 52 25.0
condition 2 10 20.0
subroutine 22 27 81.4
pod 5 11 45.4
total 143 279 51.2


line stmt bran cond sub pod time code
1             #Session functions
2             #mt 20070113 cookies should now expire
3             package CGI::Mungo::Session;
4              
5             =pod
6              
7             =head1 NAME
8              
9             CGI::Mungo::Session - Session class
10              
11             =head1 SYNOPSIS
12              
13             my $s = $mungo->getSession();
14             $s->setVar('name', 'value');
15             my $var = $s->getVar('name');
16              
17             =head1 DESCRIPTION
18              
19             Class to deal with session management.
20              
21             =head1 METHODS
22              
23             =cut
24              
25 3     3   15 use strict;
  3         4  
  3         91  
26 3     3   13 use warnings;
  3         6  
  3         124  
27 3     3   17 use Digest::MD5;
  3         6  
  3         184  
28 3     3   3178 use Data::Dumper;
  3         26443  
  3         286  
29 3     3   3141 use CGI::Simple::Cookie;
  3         21164  
  3         89  
30 3     3   31 use File::Spec;
  3         5  
  3         89  
31 3     3   17 use base qw(CGI::Mungo::Base CGI::Mungo::Log);
  3         6  
  3         6405  
32             our $prefix = "MG";
33             our $path = "/tmp";
34             ##############################################################################################################################
35             sub new{ #constructor
36 2     2 0 5 my $class = shift;
37 2         15 my $self = $class->SUPER::new();
38 2         21 $self->{'id'} = undef;
39 2         9 $self->{'error'} = "";
40 2         6 $self->{'vars'} = {};
41 2         8 return $self;
42             }
43             #########################################################################################################################
44             sub DESTROY{
45 0     0   0 __PACKAGE__->_expire(); #remove old sessions
46             }
47             #########################################################################################################################
48             sub validate{ #runs the defined sub to see if this sesion is validate
49 0     0 0 0 my $self = shift;
50 0 0       0 if($self->getVar('remoteIp')){
51 0 0       0 if($self->getVar('remoteIp') eq $ENV{'REMOTE_ADDR'}){
52 0 0 0     0 if($self->getVar('scriptPath') && $self->getVar('scriptPath') eq $ENV{'SCRIPT_NAME'}){
53 0         0 return 1;
54             }
55             else{
56 0         0 $self->log("Session " . $self->getVar('scriptPath') . " <> " . $ENV{'SCRIPT_NAME'});
57             }
58             }
59             else{
60 0         0 $self->log("Session " . $self->getVar('remoteIp') . " <> " . $ENV{'REMOTE_ADDR'});
61             }
62             }
63             else{
64 0         0 $self->log("Session has no remote IP");
65             }
66 0         0 return 0;
67             }
68             ################################################################################################################
69              
70             =head2 setVar()
71              
72             $s->setVar('name', 'value');
73              
74             Takes two arguments, first the name of the variable then the value of the variable to store.
75              
76             =cut
77              
78             ##########################################################################################################################
79             sub setVar{ #stores a variable in the session
80 4     4 1 12 my($self, $name, $value) = @_;
81 4         14 $self->_storeVar($name, $value);
82 4         11 return $self->_write();
83             }
84             ##########################################################################################################################
85             sub getVar{ #gets a stored variable from the session
86 0     0 0 0 my($self, $name) = @_;
87 0 0       0 if(defined($self->{'vars'}->{$name})){
  0         0  
88 0         0 return $self->{'vars'}->{$name};
89             }
90             else{return undef;}
91             }
92             ###########################################################################################################################
93             sub setError{
94 2     2 1 4 my($self, $error) = @_;
95 2         7 $self->{'error'} = $error; #save the error
96 2         4 return 1;
97             }
98             ###########################################################################################################################
99             sub getError{ #returns the last error
100 6     6 1 11 my $self = shift;
101 6         31 return $self->{'error'};
102             }
103             ###########################################################################################################################
104             sub setId{
105 2     2 0 5 my($self, $id) = @_;
106 2         4 $self->{'id'} = $id; #save the id
107 2         5 return 1;
108             }
109             ###########################################################################################################################
110             sub getId{ #returns the session id
111 5     5 0 7 my $self = shift;
112 5         79 return $self->{'id'};
113             }
114             ##############################################################################################################
115              
116             =head2 create()
117              
118             $response = $wf->getResponse();
119             my $hashref = {
120             username => "bob"
121             };
122             $s->create($hashref, $response);
123              
124             Creates a new session for the visitor.
125              
126             This saves the contents of the given hash reference into the session.
127              
128             The correct Set-Cookie header will be issued through the provided L object.
129              
130             =cut
131              
132             ##############################################################################################################
133             sub create{ #creates a server-side cookie for the session
134 2     2 1 5 my($self, $hash_p, $response) = @_;
135 2         9 $self->setError(""); #as we are starting a new session we clear any previous errors first
136 2         3 my $result = 0;
137 2         23 my $sessionId = time() * $$; #time in seconds * process id
138 2         23 my $ctx = Digest::MD5->new;
139 2         15 $ctx->add($sessionId);
140 2         10 $sessionId = $self->_getPrefix() . $ctx->hexdigest;
141 2         10 $self->setId($sessionId); #remember the session id
142             #set some initial values
143 2         12 $self->setVar('remoteIp', $ENV{'REMOTE_ADDR'});
144 2         11 $self->setVar('scriptPath', $ENV{'SCRIPT_NAME'});
145 2         5 while(my($name, $value) = each(%{$hash_p})){ #save any user provided variables
  2         12  
146 0         0 $self->setVar($name, $value);
147             }
148 2 50       7 if(!$self->getError()){ #all ok so far
149 2         12 my $cookie = $self->_setCookie(VALUE => $sessionId);
150 2 50       186 if($response){
151 2         44 $response->header("Set-Cookie" => $cookie);
152             }
153             else{ #old method if it is still used
154 0         0 print "Set-Cookie: " , $cookie;
155             }
156 2         247 $result = 1;
157             }
158 2         18 return $result;
159             }
160             ##############################################################################################################
161             sub read{ #read an existing session
162 2     2 0 4 my $self = shift;
163 2         4 my $result = 0;
164 2         12 my $sessionId = $self->_getCookie("SESSION"); #get the session id from the browser
165 2 50       9 if(defined($sessionId)){ #got a sessionid of some sort
166 0         0 my $prefix = $self->_getPrefix();
167 0 0       0 if($sessionId =~ m/^($prefix[a-f0-9]+)$/){ #filename valid
168 0         0 my $path = $self->_getPath();
169 0         0 my $sessionFile = File::Spec->catfile($path, $1);
170 0 0       0 if(open(SSIDE, "<", $sessionFile)){ #try to open the session file
171 0         0 my $contents = "";
172 0         0 while(){ #read each line of the file
173 0         0 $contents .= $_;
174             }
175 0         0 close(SSIDE);
176 0 0       0 if($contents =~ m/^(\$VAR1 = \{.+\};)$/m){ #check session contents
177 0         0 my $validContents = $1; #untaint variable
178 0         0 my $VAR1; #the session contents var
179             {
180 0         0 eval $validContents;
  0         0  
181             }
182 0         0 $self->{'vars'} = $VAR1;
183 0         0 $result = 1;
184 0         0 $self->setId($sessionId); #remember the session id
185             }
186             else{
187 0         0 $self->setError("Session contents invalid");
188             }
189             }
190             else{
191 0         0 $self->setError("Cant open session file: $sessionFile: $!");
192             }
193             }
194             else{
195 0         0 $self->setError("Session ID invalid: $sessionId");
196             }
197             }
198 2         9 return $result;
199             }
200             ###########################################################################################
201              
202             =pod
203              
204             =head2 delete()
205              
206             Remove the current session from memory, disk and expire it in the browser.
207              
208             =cut
209              
210             ###########################################################################################
211             sub delete{ #remove a session
212 0     0 1 0 my($self, $response) = @_;
213 0         0 my $result = 0;
214 0         0 my $sessionId = $self->getId();
215 0         0 my $prefix = $self->_getPrefix();
216 0 0       0 if($sessionId =~ m/^$prefix[a-f0-9]+$/){ #id valid
217 0         0 my $path = $self->_getPath();
218 0         0 my $sessionFile = File::Spec->catfile($path, $sessionId);
219 0 0       0 if(unlink($sessionFile)){
220 0         0 $self->log("Deleted session: $sessionId");
221 0         0 my $cookie = $self->_setCookie(EXPIRE => 'now');
222 0 0       0 if($response){
223 0         0 $response->header("Set-Cookie" , => $cookie);
224             }
225             else{
226 0         0 print "Set-Cookie: " , $cookie;
227             }
228 0         0 $self = undef; #destroy this object
229 0         0 $result = 1;
230             }
231             else{
232 0         0 $self->setError("Could not delete session");
233             }
234             }
235             else{
236 0         0 $self->setError("Session ID invalid: $sessionId");
237             }
238 0         0 return $result;
239             }
240             ###############################################################################################################
241             #private class method
242             ###############################################################################################################
243             sub _setCookie{
244 2     2   7 my($self, %options) = @_;
245 2         5 my $secure = 0;
246 2 50       10 if(exists($ENV{'HTTPS'})){ #use secure cookies if running on ssl
247 0         0 $secure = 1;
248             }
249 2   50     46 my $cookie = CGI::Simple::Cookie->new(
      50        
250             -name => 'SESSION',
251             -value => $options{'VALUE'} || undef,
252             -expires => $options{'EXPIRE'} || undef,
253             -httponly => 1,
254             -secure => $secure
255             );
256 2 50       423 if($cookie){
257 2         278 return $cookie->as_string();
258             }
259             else{
260 0         0 $self->setError("Can't create cookie");
261             }
262 0         0 return undef;
263             }
264             ##############################################################################################################
265             sub _expire{ #remove old session files
266 0     0   0 my $self = shift;
267 0         0 my $path = $self->_getPath();
268 0 0       0 if(opendir(COOKIES, $path)){
269 0         0 my @sessions = readdir(COOKIES);
270 0         0 my $expire = (time - 86400);
271 0         0 foreach(@sessions){ #check each of the cookies
272 0         0 my $prefix = $self->_getPrefix();
273 0 0       0 if($_ =~ m/^($prefix[a-f0-9]+)$/){ #found a cookie file
274 0         0 my $sessionFile = File::Spec->catfile($path, $1);
275 0         0 my @stat = stat($sessionFile);
276 0 0 0     0 if(defined($stat[9]) && $stat[9] < $expire){ #cookie is more than a day old, so remove it
277 0         0 unlink $sessionFile;
278             }
279             }
280             }
281 0         0 closedir(COOKIES);
282             }
283             }
284             ############################################################################################################
285             #private methods
286             ###########################################################################################
287             sub _write{ #writes a server-side cookie for the session
288 4     4   9 my $self = shift;
289 4         12 my $prefix = $self->_getPrefix();
290 4 50       13 if($self->getId() =~ m/^($prefix[a-f0-9]+)$/){ #filename valid
  0         0  
291 4         14 my $path = $self->_getPath();
292 4         82 my $sessionFile = File::Spec->catfile($path, $1);
293 4 50       628 if(open(SSIDE, ">", $sessionFile)){
  0         0  
294 4         9 $Data::Dumper::Freezer = 'freeze';
295 4         10 $Data::Dumper::Toaster = 'toast';
296 4         7 $Data::Dumper::Indent = 0; #turn off formatting
297 4         23 my $dump = Dumper $self->{'vars'};
298 4 50       388 if($dump){ #if we have any data
299 4         39 print SSIDE $dump;
300             }
301 4         195 close(SSIDE);
302             }
303             else{$self->setError("Cant write session: $!");}
304             }
305             else{$self->setError('Session ID invalid');}
306 4 50       17 if($self->getError()){return 0;}
  0         0  
  4         10  
307             else{return 1;}
308             }
309             ############################################################################################################
310             sub _getCookie{ #returns the value of a cookie
311 2     2   4 my $self = shift;
312 2         4 my $name = shift;
313 2         5 my $value = undef;
314 2 50       10 if(exists($ENV{'HTTP_COOKIE'})){ #we have some kind of cookie
315 0         0 my @pairs = split(/; /, $ENV{'HTTP_COOKIE'}); #this cookie might contain multiple name value pairs
316 0         0 foreach(@pairs){
317 0         0 my($n, $v) = split(/=/, $_, 2);
318 0 0       0 if($n eq $name){$value = $v;}
  0         0  
319             }
320             }
321 2         5 return $value;
322             }
323             ##########################################################################################################################
324             sub _storeVar{ #stores a variable in the session
325 4     4   9 my($self, $name, $value) = @_;
326 4 100       11 if(!defined($value)){ #remove the var
327 2 50       8 if($self->{'vars'}){
328 2         4 my %vars = %{$self->{'vars'}};
  2         8  
329 2         5 delete $vars{$name};
330 2         6 $self->{'vars'} = \%vars;
331             }
332             }
333             else{ #update/create a var
334 2         7 $self->{'vars'}->{$name} = $value; #store for later
335             }
336 4         9 return 1;
337             }
338             #####################################################################################################################
339             sub _getPrefix{ #this should be a config option
340 6     6   28 return $prefix;
341             }
342             #####################################################################################################################
343             sub _getPath{ #this should be a config option
344 4     4   9 return $path;
345             }
346             #####################################################################################################################
347              
348             =pod
349              
350             =head1 Notes
351              
352             =head1 Author
353              
354             MacGyveR
355              
356             Development questions, bug reports, and patches are welcome to the above address
357              
358             =head1 Copyright
359              
360             Copyright (c) 2013 MacGyveR. All rights reserved.
361              
362             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
363              
364             =cut
365              
366             ##########################################
367             return 1;
368 3     3   4912 END {}