File Coverage

blib/lib/CGI/Session/Driver/file.pm
Criterion Covered Total %
statement 83 91 91.2
branch 29 50 58.0
condition 6 15 40.0
subroutine 15 15 100.0
pod 5 5 100.0
total 138 176 78.4


line stmt bran cond sub pod time code
1             package CGI::Session::Driver::file;
2              
3             # $Id$
4              
5 24     24   3150 use strict;
  24         45  
  24         1059  
6              
7 24     24   137 use Carp;
  24         37  
  24         1940  
8 24     24   136 use File::Spec;
  24         40  
  24         769  
9 24     24   121 use Fcntl qw( :DEFAULT :flock :mode );
  24         210  
  24         37097  
10 24     24   16230 use CGI::Session::Driver;
  24         62  
  24         1012  
11 24     24   149 use vars qw( $FileName $NoFlock $UMask $NO_FOLLOW );
  24         40  
  24         2599  
12              
13             BEGIN {
14             # keep historical behavior
15              
16 24     24   127 no strict 'refs';
  24         42  
  24         909  
17            
18 24     24   37318 *FileName = \$CGI::Session::File::FileName;
19             }
20              
21             @CGI::Session::Driver::file::ISA = ( "CGI::Session::Driver" );
22             $CGI::Session::Driver::file::VERSION = '4.43';
23             $FileName = "cgisess_%s";
24             $NoFlock = 0;
25             $UMask = 0660;
26             $NO_FOLLOW = eval { O_NOFOLLOW } || 0;
27              
28             sub init {
29 37     37 1 84 my $self = shift;
30 37   66     1041 $self->{Directory} ||= File::Spec->tmpdir();
31              
32 37 50       942 unless ( -d $self->{Directory} ) {
33 0         0 require File::Path;
34 0 0       0 unless ( File::Path::mkpath($self->{Directory}) ) {
35 0         0 return $self->set_error( "init(): couldn't create directory path: $!" );
36             }
37             }
38            
39 37 50       188 $self->{NoFlock} = $NoFlock unless exists $self->{NoFlock};
40 37 50       168 $self->{UMask} = $UMask unless exists $self->{UMask};
41            
42 37         354 return 1;
43             }
44              
45             sub _file {
46 55     55   114 my ($self,$sid) = @_;
47 55         92 my $id = $sid;
48 55         135 $id =~ s|\\|/|g;
49              
50 55 50       300 if ($id =~ m|/|)
51             {
52 0         0 return $self->set_error( "_file(): Session ids cannot contain \\ or / chars: $sid" );
53             }
54              
55 55         1187 return File::Spec->catfile($self->{Directory}, sprintf( $FileName, $sid ));
56             }
57              
58             sub retrieve {
59 24     24 1 53 my $self = shift;
60 24         53 my ($sid) = @_;
61              
62 24         91 my $path = $self->_file($sid);
63            
64 24 100       736 return 0 unless -e $path;
65              
66             # make certain our filehandle goes away when we fall out of scope
67 15         47 local *FH;
68              
69 15 50       240 if (-l $path) {
70 0 0       0 unlink($path) or
71             return $self->set_error("retrieve(): '$path' appears to be a symlink and I couldn't remove it: $!");
72 0         0 return 0; # we deleted this so we have no hope of getting back anything
73             }
74 15 50       701 sysopen(FH, $path, O_RDONLY | $NO_FOLLOW ) || return $self->set_error( "retrieve(): couldn't open '$path': $!" );
75            
76 15 50 33     237 $self->{NoFlock} || flock(FH, LOCK_SH) or return $self->set_error( "retrieve(): couldn't lock '$path': $!" );
77              
78 15         37 my $rv = "";
79 15         1645 while ( <FH> ) {
80 71         267 $rv .= $_;
81             }
82 15         907 close(FH);
83 15         126 return $rv;
84             }
85              
86              
87              
88             sub store {
89 22     22 1 52 my $self = shift;
90 22         53 my ($sid, $datastr) = @_;
91            
92 22         97 my $path = $self->_file($sid);
93            
94             # make certain our filehandle goes away when we fall out of scope
95 22         98 local *FH;
96            
97 22         55 my $mode = O_WRONLY|$NO_FOLLOW;
98            
99             # kill symlinks when we spot them
100 22 100       3717 if (-l $path) {
101 1 50       63 unlink($path) or
102             return $self->set_error("store(): '$path' appears to be a symlink and I couldn't remove it: $!");
103             }
104            
105 22 100       307 $mode = O_RDWR|O_CREAT|O_EXCL unless -e $path;
106            
107 22 50       2622 sysopen(FH, $path, $mode, $self->{UMask}) or return $self->set_error( "store(): couldn't open '$path': $!" );
108            
109             # sanity check to make certain we're still ok
110 22 50       377 if (-l $path) {
111 0         0 return $self->set_error("store(): '$path' is a symlink, check for malicious processes");
112             }
113            
114             # prevent race condition (RT#17949)
115 22 50 33     768 $self->{NoFlock} || flock(FH, LOCK_EX) or return $self->set_error( "store(): couldn't lock '$path': $!" );
116 22 50       1282 truncate(FH, 0) or return $self->set_error( "store(): couldn't truncate '$path': $!" );
117            
118 22         160 print FH $datastr;
119 22 50       112790 close(FH) or return $self->set_error( "store(): couldn't close '$path': $!" );
120 22         192 return 1;
121             }
122              
123              
124             sub remove {
125 7     7 1 45 my $self = shift;
126 7         19 my ($sid) = @_;
127 7         27 my $path = $self -> _file($sid);
128 7 50       1196 unlink($path) or return $self->set_error( "remove(): couldn't unlink '$path': $!" );
129 7         112 return 1;
130             }
131              
132              
133             sub traverse {
134 1     1 1 2 my $self = shift;
135 1         3 my ($coderef) = @_;
136              
137 1 50 33     13 unless ( $coderef && ref($coderef) && (ref $coderef eq 'CODE') ) {
      33        
138 0         0 croak "traverse(): usage error";
139             }
140              
141 1 50       30 opendir( DIRHANDLE, $self->{Directory} )
142             or return $self->set_error( "traverse(): couldn't open $self->{Directory}, " . $! );
143              
144 1         3 my $filename_pattern = $FileName;
145 1         3 $filename_pattern =~ s/\./\\./g;
146 1         5 $filename_pattern =~ s/\%s/(\.\+)/g;
147 1         137 while ( my $filename = readdir(DIRHANDLE) ) {
148 53 100       111 next if $filename =~ m/^\.\.?$/;
149 51         378 my $full_path = File::Spec->catfile($self->{Directory}, $filename);
150 51 50       1184 my $mode = (stat($full_path))[2]
151             or return $self->set_error( "traverse(): stat failed for $full_path: " . $! );
152 51 100       146 next if S_ISDIR($mode);
153 50 100       557 if ( $filename =~ /^$filename_pattern$/ ) {
154 1         6 $coderef->($1);
155             }
156             }
157 1         23 closedir( DIRHANDLE );
158 1         6 return 1;
159             }
160              
161              
162             sub DESTROY {
163 37     37   972 my $self = shift;
164             }
165              
166             1;
167              
168             __END__;
169              
170             =pod
171              
172             =head1 NAME
173              
174             CGI::Session::Driver::file - Default CGI::Session driver
175              
176             =head1 SYNOPSIS
177              
178             $s = CGI::Session->new();
179             $s = CGI::Session->new("driver:file", $sid);
180             $s = CGI::Session->new("driver:file", $sid, {Directory=>'/tmp'});
181              
182              
183             =head1 DESCRIPTION
184              
185             When CGI::Session object is created without explicitly setting I<driver>, I<file> will be assumed.
186             I<file> - driver will store session data in plain files, where each session will be stored in a separate
187             file.
188              
189             Naming conventions of session files are defined by C<$CGI::Session::Driver::file::FileName> global variable.
190             Default value of this variable is I<cgisess_%s>, where %s will be replaced with respective session ID. Should
191             you wish to set your own FileName template, do so before requesting for session object:
192              
193             use CGI::Session::Driver::file; # This line is mandatory.
194             # Time passes...
195             $CGI::Session::Driver::file::FileName = "%s.dat";
196             $s = CGI::Session->new();
197              
198             For backwards compatibility with 3.x, you can also use the variable name
199             C<$CGI::Session::File::FileName>, which will override the one above.
200              
201             =head2 DRIVER ARGUMENTS
202              
203             If you wish to specify a session directory, use the B<Directory> option, which denotes location of the directory
204             where session ids are to be kept. If B<Directory> is not set, defaults to whatever File::Spec->tmpdir() returns.
205             So all the three lines in the SYNOPSIS section of this manual produce the same result on a UNIX machine.
206              
207             If specified B<Directory> does not exist, all necessary directory hierarchy will be created.
208              
209             By default, sessions are created with a umask of 0660. If you wish to change the umask for a session, pass
210             a B<UMask> option with an octal representation of the umask you would like for said session.
211              
212             =head1 NOTES
213              
214             If your OS doesn't support flock, you should understand the risks of going without locking the session files. Since
215             sessions tend to be used in environments where race conditions may occur due to concurrent access of files by
216             different processes, locking tends to be seen as a good and very necessary thing. If you still want to use this
217             driver but don't want flock, set C<$CGI::Session::Driver::file::NoFlock> to 1 or pass C<< NoFlock => 1 >> and this
218             driver will operate without locks.
219              
220             =head1 LICENSING
221              
222             For support and licensing see L<CGI::Session|CGI::Session>
223              
224             =cut