File Coverage

blib/lib/Apache/Session.pm
Criterion Covered Total %
statement 85 130 65.3
branch 17 28 60.7
condition 13 17 76.4
subroutine 12 37 32.4
pod 0 26 0.0
total 127 238 53.3


line stmt bran cond sub pod time code
1             #############################################################################
2             #
3             # Apache::Session
4             # Apache persistent user sessions
5             # Copyright(c) 1998, 1999, 2000, 2001, 2004 Jeffrey William Baker (jwbaker@acm.org)
6             # Distribute under the Artistic License
7             #
8             #############################################################################
9              
10             =head1 NAME
11              
12             Apache::Session - A persistence framework for session data
13              
14             =head1 SYNOPSIS
15              
16             use Apache::Session::MySQL;
17            
18             my %session;
19            
20             #make a fresh session for a first-time visitor
21             tie %session, 'Apache::Session::MySQL';
22              
23             #stick some stuff in it
24             $session{visa_number} = "1234 5678 9876 5432";
25            
26             #get the session id for later use
27             my $id = $session{_session_id};
28            
29             #...time passes...
30            
31             #get the session data back out again during some other request
32             my %session;
33             tie %session, 'Apache::Session::MySQL', $id;
34            
35             &validate($session{visa_number});
36            
37             #delete a session from the object store permanently
38             tied(%session)->delete;
39            
40              
41             =head1 DESCRIPTION
42              
43             Apache::Session is a persistence framework which is particularly useful
44             for tracking session data between httpd requests. Apache::Session is
45             designed to work with Apache and mod_perl, but it should work under
46             CGI and other web servers, and it also works outside of a web server
47             altogether.
48              
49             Apache::Session consists of five components: the interface, the object store,
50             the lock manager, the ID generator, and the serializer. The interface is
51             defined in Session.pm, which is meant to be easily subclassed. The object
52             store can be the filesystem, a Berkeley DB, a MySQL DB, an Oracle DB, a
53             Postgres DB, Sybase, or Informix. Locking is done by lock files, semaphores, or
54             the locking capabilities of the various databases. Serialization is done via
55             Storable, and optionally ASCII-fied via MIME or pack(). ID numbers are
56             generated via MD5. The reader is encouraged to extend these capabilities to
57             meet his own requirements.
58              
59             A derived class of Apache::Session is used to tie together the three
60             components. The derived class inherits the interface from Apache::Session, and
61             specifies which store and locker classes to use. Apache::Session::MySQL, for
62             instance, uses the MySQL storage class and also the MySQL locking class. You
63             can easily plug in your own object store or locker class.
64              
65             =head1 INTERFACE
66              
67             The interface to Apache::Session is very simple: tie a hash to the
68             desired class and use the hash as normal. The constructor takes two
69             optional arguments. The first argument is the desired session ID
70             number, or undef for a new session. The second argument is a hash
71             of options that will be passed to the object store and locker classes.
72              
73             =head2 tieing the session
74              
75             Get a new session using DBI:
76              
77             tie %session, 'Apache::Session::MySQL', undef,
78             { DataSource => 'dbi:mysql:sessions' };
79            
80             Restore an old session from the database:
81              
82             tie %session, 'Apache::Session::MySQL', $session_id,
83             { DataSource => 'dbi:mysql:sessions' };
84              
85              
86             =head2 Storing and retrieving data to and from the session
87              
88             Hey, how much easier could it get?
89              
90             $session{first_name} = "Chuck";
91             $session{an_array_ref} = [ $one, $two, $three ];
92             $session{an_object} = new Some::Class;
93              
94             =head2 Reading the session ID
95              
96             The session ID is the only magic entry in the session object,
97             but anything beginning with a "_" is considered reserved for
98             future use.
99              
100             my $id = $session{_session_id};
101              
102             =head2 Permanently removing the session from storage
103              
104             tied(%session)->delete;
105              
106             =head1 BEHAVIOR
107              
108             Apache::Session tries to behave the way the author believes that
109             you would expect. When you create a new session, Session immediately
110             saves the session to the data store, or calls die() if it cannot. It
111             also obtains an exclusive lock on the session object. If you retrieve
112             an existing session, Session immediately restores the object from storage,
113             or calls die() in case of an error. Session also obtains an non-exclusive
114             lock on the session.
115              
116             As you put data into the session hash, Session squirrels it away for
117             later use. When you untie() the session hash, or it passes out of
118             scope, Session checks to see if anything has changed. If so, Session
119             gains an exclusive lock and writes the session to the data store.
120             It then releases any locks it has acquired.
121              
122             Note that Apache::Session does only a shallow check to see if anything has
123             changed. If nothing changes in the top level tied hash, the data will not be
124             updated in the backing store. You are encouraged to timestamp the session hash
125             so that it is sure to be updated.
126              
127             When you call the delete() method on the session object, the
128             object is immediately removed from the object store, if possible.
129              
130             When Session encounters an error, it calls die(). You will probably
131             want to wrap your session logic in an eval block to trap these errors.
132              
133             =head1 LOCKING AND TRANSACTIONS
134              
135             By default, most Apache::Session implementations only do locking to prevent
136             data corruption. The locking scheme does not provide transactional
137             consistency, such as you might get from a relational database. If you desire
138             transactional consistency, you must provide the Transaction argument with a
139             true value when you tie the session hash. For example:
140              
141             tie %s, 'Apache::Session::File', $id {
142             Directory => '/tmp/sessions',
143             LockDirectory => '/var/lock/sessions',
144             Transaction => 1
145             };
146              
147             Note that the Transaction argument has no practical effect on the MySQL and
148             Postgres implementations. The MySQL implementation only supports exclusive
149             locking, and the Postgres implementation uses the transaction features of that
150             database.
151              
152             =head1 IMPLEMENTATION
153              
154             The way you implement Apache::Session depends on what you are
155             trying to accomplish. Here are some hints on which classes to
156             use in what situations
157              
158             =head1 STRATEGIES
159              
160             Apache::Session is mainly designed to track user session between
161             http requests. However, it can also be used for any situation
162             where data persistence is desirable. For example, it could be
163             used to share global data between your httpd processes. The
164             following examples are short mod_perl programs which demonstrate
165             some session handling basics.
166              
167             =head2 Sharing data between Apache processes
168              
169             When you share data between Apache processes, you need to decide on a
170             session ID number ahead of time and make sure that an object with that
171             ID number is in your object store before starting you Apache. How you
172             accomplish that is your own business. I use the session ID "1". Here
173             is a short program in which we use Apache::Session to store out
174             database access information.
175              
176             use Apache;
177             use Apache::Session::File;
178             use DBI;
179              
180             use strict;
181            
182             my %global_data;
183            
184             eval {
185             tie %global_data, 'Apache::Session::File', 1,
186             {Directory => '/tmp/sessiondata'};
187             };
188             if ($@) {
189             die "Global data is not accessible: $@";
190             }
191              
192             my $dbh = DBI->connect($global_data{datasource},
193             $global_data{username}, $global_data{password}) || die $DBI::errstr;
194              
195             undef %global_data;
196            
197             #program continues...
198            
199             As shown in this example, you should undef or untie your session hash
200             as soon as you are done with it. This will free up any locks associated
201             with your process.
202              
203             =head2 Tracking users with cookies
204              
205             The choice of whether to use cookies or path info to track user IDs
206             is a rather religious topic among Apache users. This example uses cookies.
207             The implementation of a path info system is left as an exercise for the
208             reader.
209              
210             Note that Apache::Session::Generate::ModUsertrack uses Apache's mod_usertrack
211             cookies to generate and maintain session IDs.
212              
213             use Apache::Session::MySQL;
214             use Apache;
215              
216             use strict;
217              
218             #read in the cookie if this is an old session
219              
220             my $r = Apache->request;
221             my $cookie = $r->header_in('Cookie');
222             $cookie =~ s/SESSION_ID=(\w*)/$1/;
223              
224             #create a session object based on the cookie we got from the browser,
225             #or a new session if we got no cookie
226              
227             my %session;
228             tie %session, 'Apache::Session::MySQL', $cookie, {
229             DataSource => 'dbi:mysql:sessions', #these arguments are
230             UserName => 'mySQL_user', #required when using
231             Password => 'password', #MySQL.pm
232             LockDataSource => 'dbi:mysql:sessions',
233             LockUserName => 'mySQL_user',
234             LockPassword => 'password'
235             };
236              
237             #Might be a new session, so lets give them their cookie back
238              
239             my $session_cookie = "SESSION_ID=$session{_session_id};";
240             $r->header_out("Set-Cookie" => $session_cookie);
241              
242             #program continues...
243              
244             =head1 SEE ALSO
245              
246             Apache::Session::MySQL, Apache::Session::Postgres, Apache::Session::File,
247             Apache::Session::DB_File, Apache::Session::Oracle, Apache::Session::Sybase
248              
249             The O Reilly book "Apache Modules in Perl and C", by Doug MacEachern and
250             Lincoln Stein, has a chapter on keeping state.
251              
252             =head1 AUTHORS
253              
254             Jeffrey Baker is the author of
255             Apache::Session.
256              
257             Tatsuhiko Miyagawa is the author of
258             Generate::ModUniqueID and Generate::ModUsertrack
259              
260             Erik Rantapaa found errors in both Lock::File
261             and Store::File
262              
263             Bart Schaefer notified me of a bug in
264             Lock::File.
265              
266             Chris Winters contributed the Sybase code.
267              
268             Michael Schout fixed a commit policy bug in 1.51.
269              
270             Andreas J. Koenig contributed valuable CPAN
271             advice and also Apache::Session::Tree and Apache::Session::Counted.
272              
273             Gerald Richter had the idea for a tied hash interface
274             and provided the initial code for it. He also uses Apache::Session in
275             his Embperl module and is the author of Apache::Session::Embperl
276              
277             Jochen Wiedmann contributed patches for bugs and
278             improved performance.
279              
280             Steve Shreeve squashed a bug in 0.99.0 whereby
281             a cleared hash or deleted key failed to set the modified bit.
282              
283             Peter Kaas sent quite a bit of feedback
284             with ideas for interface improvements.
285              
286             Randy Harmon contributed the original
287             storage-independent object interface with input from:
288              
289             Bavo De Ridder
290             Jules Bean
291             Lincoln Stein
292              
293             Jamie LeTaul fixed file locking on Windows.
294              
295             Scott McWhirter contributed verbose error messages for
296             file locking.
297              
298             Corris Randall gave us the option to use any table name in
299             the MySQL store.
300              
301             Oliver Maul updated the Sybase modules
302              
303             Innumerable users sent a patch for the reversed file age test in the file
304             locking module.
305              
306             Langen Mike contributed Informix modules.
307              
308             =cut
309              
310             package Apache::Session;
311              
312 1     1   6 use strict;
  1         1  
  1         47  
313 1     1   6 use vars qw($VERSION);
  1         2  
  1         2038  
314              
315             $VERSION = '1.81_01';
316              
317             #State constants
318             #
319             #These constants are used in a bitmask to store the
320             #object's status. New indicates that the object
321             #has not yet been inserted into the object store.
322             #Modified indicates that a member value has been
323             #changed. Deleted is set when delete() is called.
324             #Synced indicates that an object has been materialized
325             #from the datastore.
326              
327             sub NEW () {1};
328             sub MODIFIED () {2};
329             sub DELETED () {4};
330             sub SYNCED () {8};
331              
332              
333              
334             #State methods
335             #
336             #These methods aren't used anymore for performance reasons. I'll
337             #keep them around for reference
338              
339              
340              
341 0     0 0 0 sub is_new { $_[0]->{status} & NEW }
342 0     0 0 0 sub is_modified { $_[0]->{status} & MODIFIED }
343 0     0 0 0 sub is_deleted { $_[0]->{status} & DELETED }
344 0     0 0 0 sub is_synced { $_[0]->{status} & SYNCED }
345              
346 0     0 0 0 sub make_new { $_[0]->{status} |= NEW }
347 0     0 0 0 sub make_modified { $_[0]->{status} |= MODIFIED }
348 0     0 0 0 sub make_deleted { $_[0]->{status} |= DELETED }
349 0     0 0 0 sub make_synced { $_[0]->{status} |= SYNCED }
350              
351 0     0 0 0 sub make_old { $_[0]->{status} &= ($_[0]->{status} ^ NEW) }
352 0     0 0 0 sub make_unmodified { $_[0]->{status} &= ($_[0]->{status} ^ MODIFIED) }
353 0     0 0 0 sub make_undeleted { $_[0]->{status} &= ($_[0]->{status} ^ DELETED) }
354 0     0 0 0 sub make_unsynced { $_[0]->{status} &= ($_[0]->{status} ^ SYNCED) }
355              
356              
357              
358             #Tie methods
359             #
360             #Here we are hiding our complex data persistence framework behind
361             #a simple hash. See the perltie manpage.
362              
363              
364              
365             sub TIEHASH {
366 3     3   898 my $class = shift;
367            
368 3         5 my $session_id = shift;
369 3   50     11 my $args = shift || {};
370              
371             #Set-up the data structure and make it an object
372             #of our class
373            
374 3         26 my $self = {
375             args => $args,
376             data => { _session_id => $session_id },
377             serialized => undef,
378             lock => 0,
379             status => 0,
380             lock_manager => undef, # These two are object refs ...
381             object_store => undef,
382             generate => undef, # but these three are subroutine refs
383             serialize => undef,
384             unserialize => undef,
385             };
386            
387 3         7 bless $self, $class;
388              
389 3         13 $self->populate;
390              
391              
392             #If a session ID was passed in, this is an old hash.
393             #If not, it is a fresh one.
394              
395 3 100 66     18 if (defined $session_id && $session_id) {
396            
397             #check the session ID for remote exploitation attempts
398             #this will die() on suspicious session IDs.
399              
400 2         4 &{$self->{validate}}($self);
  2         9  
401            
402 1 0 33     4 if (exists $args->{Transaction} && $args->{Transaction}) {
403 0         0 $self->acquire_write_lock;
404             }
405            
406 1         2 $self->{status} &= ($self->{status} ^ NEW);
407 1         7 $self->restore;
408             }
409             else {
410 1         3 $self->{status} |= NEW;
411 1         2 &{$self->{generate}}($self);
  1         8  
412 1         11 $self->save;
413             }
414            
415 2         8 return $self;
416             }
417              
418             sub FETCH {
419 5     5   1656 my $self = shift;
420 5         7 my $key = shift;
421            
422 5         24 return $self->{data}->{$key};
423             }
424              
425             sub STORE {
426 2     2   21 my $self = shift;
427 2         3 my $key = shift;
428 2         3 my $value = shift;
429            
430 2         5 $self->{data}->{$key} = $value;
431            
432 2         3 $self->{status} |= MODIFIED;
433            
434 2         6 return $self->{data}->{$key};
435             }
436              
437             sub DELETE {
438 0     0   0 my $self = shift;
439 0         0 my $key = shift;
440            
441 0         0 $self->{status} |= MODIFIED;
442            
443 0         0 delete $self->{data}->{$key};
444             }
445              
446             sub CLEAR {
447 0     0   0 my $self = shift;
448              
449 0         0 $self->{status} |= MODIFIED;
450            
451 0         0 $self->{data} = {};
452             }
453              
454             sub EXISTS {
455 1     1   539 my $self = shift;
456 1         3 my $key = shift;
457            
458 1         6 return exists $self->{data}->{$key};
459             }
460              
461             sub FIRSTKEY {
462 0     0   0 my $self = shift;
463            
464 0         0 my $reset = keys %{$self->{data}};
  0         0  
465 0         0 return each %{$self->{data}};
  0         0  
466             }
467              
468             sub NEXTKEY {
469 0     0   0 my $self = shift;
470            
471 0         0 return each %{$self->{data}};
  0         0  
472             }
473              
474             sub DESTROY {
475 0     0   0 my $self = shift;
476            
477 0         0 $self->save;
478 0         0 $self->release_all_locks;
479             }
480              
481              
482              
483             #
484             #Persistence methods
485             #
486              
487              
488             sub restore {
489 1     1 0 1 my $self = shift;
490            
491 1 50       5 return if ($self->{status} & SYNCED);
492 1 50       4 return if ($self->{status} & NEW);
493            
494 1         8 $self->acquire_read_lock;
495              
496 1         5 $self->{object_store}->materialize($self);
497 1         2 &{$self->{unserialize}}($self);
  1         5  
498            
499 1         39 $self->{status} &= ($self->{status} ^ MODIFIED);
500 1         2 $self->{status} |= SYNCED
501             }
502              
503             sub save {
504 5     5 0 7 my $self = shift;
505            
506             return unless (
507             $self->{status} & MODIFIED ||
508             $self->{status} & NEW ||
509 5 100 100     46 $self->{status} & DELETED
      100        
510             );
511            
512 3         11 $self->acquire_write_lock;
513              
514 3 100       9 if ($self->{status} & DELETED) {
515 1         8 $self->{object_store}->remove($self);
516 1         2 $self->{status} |= SYNCED;
517 1         3 $self->{status} &= ($self->{status} ^ MODIFIED);
518 1         3 $self->{status} &= ($self->{status} ^ DELETED);
519 1         3 return;
520             }
521 2 100       10 if ($self->{status} & MODIFIED) {
522 1         2 &{$self->{serialize}}($self);
  1         4  
523 1         56 $self->{object_store}->update($self);
524 1         4 $self->{status} &= ($self->{status} ^ MODIFIED);
525 1         2 $self->{status} |= SYNCED;
526 1         3 return;
527             }
528 1 50       4 if ($self->{status} & NEW) {
529 1         2 &{$self->{serialize}}($self);
  1         5  
530 1         80 $self->{object_store}->insert($self);
531 1         3 $self->{status} &= ($self->{status} ^ NEW);
532 1         3 $self->{status} |= SYNCED;
533 1         2 $self->{status} &= ($self->{status} ^ MODIFIED);
534 1         2 return;
535             }
536             }
537              
538             sub delete {
539 1     1 0 13835 my $self = shift;
540            
541 1 50       6 return if ($self->{status} & NEW);
542            
543 1         2 $self->{status} |= DELETED;
544 1         5 $self->save;
545             }
546              
547              
548              
549             #
550             #Locking methods
551             #
552              
553             sub READ_LOCK () {1};
554             sub WRITE_LOCK () {2};
555              
556              
557             #These methods aren't used anymore for performance reasons. I'll keep them
558             #around for reference.
559              
560 0     0 0 0 sub has_read_lock { $_[0]->{lock} & READ_LOCK }
561 0     0 0 0 sub has_write_lock { $_[0]->{lock} & WRITE_LOCK }
562              
563 0     0 0 0 sub set_read_lock { $_[0]->{lock} |= READ_LOCK }
564 0     0 0 0 sub set_write_lock { $_[0]->{lock} |= WRITE_LOCK }
565              
566 0     0 0 0 sub unset_read_lock { $_[0]->{lock} &= ($_[0]->{lock} ^ READ_LOCK) }
567 0     0 0 0 sub unset_write_lock { $_[0]->{lock} &= ($_[0]->{lock} ^ WRITE_LOCK) }
568              
569             sub acquire_read_lock {
570 1     1 0 2 my $self = shift;
571              
572 1 50       4 return if ($self->{lock} & READ_LOCK);
573              
574 1         4 $self->{lock_manager}->acquire_read_lock($self);
575              
576 1         1 $self->{lock} |= READ_LOCK;
577             }
578              
579             sub acquire_write_lock {
580 3     3 0 6 my $self = shift;
581              
582 3 100       12 return if ($self->{lock} & WRITE_LOCK);
583              
584 2         13 $self->{lock_manager}->acquire_write_lock($self);
585              
586 2         4 $self->{lock} |= WRITE_LOCK;
587             }
588              
589             sub release_read_lock {
590 0     0 0 0 my $self = shift;
591              
592 0 0       0 return unless ($self->{lock} & READ_LOCK);
593              
594 0         0 $self->{lock_manager}->release_read_lock($self);
595              
596 0         0 $self->{lock} &= ($self->{lock} ^ READ_LOCK);
597             }
598              
599             sub release_write_lock {
600 0     0 0 0 my $self = shift;
601              
602 0 0       0 return unless ($self->{lock} & WRITE_LOCK);
603              
604 0         0 $self->{lock_manager}->release_write_lock($self);
605            
606 0         0 $self->{lock} &= ($self->{lock} ^ WRITE_LOCK);
607             }
608              
609             sub release_all_locks {
610 3     3 0 4 my $self = shift;
611            
612 3 100 100     25 return unless ($self->{lock} & READ_LOCK || $self->{lock} & WRITE_LOCK);
613            
614 2         12 $self->{lock_manager}->release_all_locks($self);
615              
616 2         4 $self->{lock} &= ($self->{lock} ^ READ_LOCK);
617 2         22 $self->{lock} &= ($self->{lock} ^ WRITE_LOCK);
618             }
619              
620             1;