File Coverage

blib/lib/Catalyst/Plugin/Session/CGISession.pm
Criterion Covered Total %
statement 39 174 22.4
branch 0 74 0.0
condition 0 41 0.0
subroutine 13 32 40.6
pod 12 12 100.0
total 64 333 19.2


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::Session::CGISession;
2              
3 1     1   20402 use warnings;
  1         2  
  1         35  
4 1     1   6 use strict;
  1         2  
  1         56  
5              
6             our $VERSION = '0.04';
7              
8 1     1   6 use base qw/Class::Data::Inheritable Class::Accessor::Fast/;
  1         7  
  1         1078  
9 1     1   356662 use CGI::Session;
  1         10746  
  1         7  
10 1     1   8335 use NEXT;
  1         10427  
  1         41  
11 1     1   14 use Carp;
  1         3  
  1         92  
12 1     1   8 use File::Spec;
  1         2  
  1         25  
13 1     1   1581 use URI;
  1         5757  
  1         39  
14 1     1   1062 use URI::Find;
  1         2735  
  1         78  
15 1     1   1320 use Data::Dumper;
  1         7446  
  1         171  
16              
17              
18             # QUESTIONS:
19             #
20             #
21             # Shouldn't the body text rewrite in finalize be limited to
22             # content-type qr{text/x?html} ?
23             #
24             # Should extracting embedded session ids from paths be conditional
25             # on {rewrite}? That is, we shouldn't do it unless allowed
26             # by config.
27             #
28             # How can someone say "no expiration should be done"? We can't (yet)
29             # use the value zero. The Cache modules use the value
30             # $EXPIRES_NEVER = 'never'
31             #
32             # If session plugins must be setup() before other plugins that use
33             # session data, then doesn't that also force calls to finalize()
34             # in the session plugins before the others? So don't we need
35             # to explicitly state other plugins should not expect that
36             # they will be able to alter session data? (perhaps we should
37             # have a croak() guard against this?)
38             #
39             # Need to trace what happens when ->session() is not called.
40             # We don't do any CGIS processing? (we shouldn't)
41             # (but see that Authentication::CDBI *always* calls session)
42             #
43             #
44             # ANSWERS: (partial or otherwise)
45             #
46             # Is there any way to prevent session processing when static content is
47             # (about to be) served? Hmm, Static::Simple hooks into dispatch()
48             # which is called after all the prepare steps. And session() is
49             # being called from Authentication::CDBI::prepare_action()
50             # * AndyG changes Static::Simple to hook into prepare_action() chain
51             # and short-circuit that to avoid session access on static files.
52             #
53             # Check out the ramifications of ip_match and remote_addr - do we need
54             # to disable or allow disabling of the ip_match checks? If enabled
55             # do we need to override remote_addr?
56             # $CGI::Session::IP_MATCH = 0;
57             # _SESSION_REMOTE_ADDR => $ENV{REMOTE_ADDR} || "",
58             # if($CGI::Session::IP_MATCH) {
59             # unless($self->_ip_matches) {
60             # sub _ip_matches {
61             # return ( $_[0]->{_DATA}->{_SESSION_REMOTE_ADDR} eq $ENV{REMOTE_ADDR} );
62             # sub remote_addr { return $_[0]->{_DATA}->{_SESSION_REMOTE_ADDR} }
63             # * okay, so this feature is disabled by default, as per discussion
64             # in the CGIS docs (tutorial in 4.x). If the user wants to turn
65             # this on they can do so using the global variable
66             #
67             # Do we want to provide the CGIS specific APIs like ->param() ??
68             # Umm, actually this might be _required_ for some people who
69             # are migrating to Catalyst with existing code/assumptions.
70             # If it is called ::CGISession we probably have to supply
71             # the basics _of_ CGI::Session.
72             # yes: param() is_new() flush()
73             # no: load_param() save_param()
74             # ???: delete()
75             # * We will do param/is_new/flush to start with.
76             #
77             #
78             # SPECULATION:
79             #
80             # This might mitigate the lack of locks
81             # We could do the no_write_on_close feature if we use the CGIS 4.x
82             # undocumented internal method _reset_status() to clear the modified
83             # flags. There is also _unset_status()
84             # Isn't it true that merely reading a session object marks it as
85             # 'modified' and thus must be written out again, simply because the
86             # "last access time" has been updated?
87             #
88             #
89             # Where to document the similarities with C::P::Session::FastMmap,
90             # such as:
91             # - same cookie name 'session' used
92             # - same session hash data access method ->session->{}
93             # And differences:
94             # - URL embedded session id checking is stricter
95             # - session expires time reset by access (expiration time is
96             # relative to last access, not session creation)
97              
98              
99              
100             our $DEFAULT_EXPIRATION_TIME = 60 * 60 * 24;
101              
102             # We default the CGI::Session storage to plain files in temp directory
103             # We use 'File' 'Storable' 'MD5' to match CGIS 3.x case-sensitivity
104             our $DEFAULT_CGI_SESSION_DSN = 'driver:File;serializer:Storable;id:MD5';
105             our $DEFAULT_CGI_SESSION_OPTIONS = { Directory => File::Spec->tmpdir };
106              
107             # This is the parameter name used with CGI::Session::param() where we
108             # stuff our session data hash that is exposed with our session() method.
109             our $SESSION_DATA_PARAMETER_NAME = '_catalyst_session';
110              
111              
112 1     1   8 use constant SESSION_DUMP_DATA => 1;
  1         3  
  1         52  
113 1     1   5 use constant SESSION_DUMP_PARAMS => 2;
  1         2  
  1         37  
114 1     1   6 use constant SESSION_DUMP_SESSION => 3;
  1         2  
  1         2013  
115              
116              
117             __PACKAGE__->mk_accessors('sessionid');
118              
119              
120             # - - - - - - - - - - - - - - - - - - - - - - - -
121             # Create a dummy class to satisfy CGI::Session need for a "query object".
122             #
123             # If a session id string is not given in the CGI::Session->new() call,
124             # CGIS will attempt to discover the session id value by its own means.
125             # In spite of the fact that we may have already determined that there
126             # is no incoming session id, CGIS will try anyway - there is no way
127             # to turn off its discovery actions.
128             #
129             # The great problem is that CGIS will try to load CGI.pm and execute a
130             # calls to CGI->new(). We must prevent this.
131             #
132             # One way to prevent this is to supply a "query object" parameter. CGIS
133             # will call this object's param() and cookie() methods checking to see
134             # whether a parameter or cookie named 'CGISESSID' was in the request.
135             #
136             # We can certainly use the request object, $c->request, to serve as the
137             # query object. It will field the param() and cookie() requests quite
138             # handily. However unlikely, though, it is possible that an application
139             # might use a parameter or cookie with the sought after name, and a
140             # false 'hit' would happen. It is even more unlikely that the value
141             # would look like a session id value, but double accidents happen also.
142             #
143             # We can avoid all the nasty possibilities by defining our own dummy
144             # query object. To all queries to param() or cookie() we return undef.
145             # Thus CGIS is finally convinced about what we know already, there is
146             # no session id available.
147              
148             package Catalyst::Plugin::Session::CGISession::dummy_query;
149              
150             sub new {
151 0     0     my $class = shift;
152 0           return bless {}, $class;
153             }
154              
155 0     0     sub param { return; }
156 0     0     sub cookie { return; }
157              
158             # CgiS::dummy_query::cookie(::dummy_query=HASH(0x1ef8b30)|CGISESSID) called ...
159             # at C:/Perl587/site/lib/CGI/Session.pm line 640
160             # CgiS::dummy_query::param(::dummy_query=HASH(0x1ef8b30)|CGISESSID) called ...
161             # at C:/Perl587/site/lib/CGI/Session.pm line 640
162              
163             package Catalyst::Plugin::Session::CGISession;
164              
165              
166             # - - - - - - - - - - - - - - - - - - - - - - - -
167              
168             # This method is called from Catalyst::Setup at plugin initialization time.
169             # It is expected that all configuration values for session have already
170             # been set.
171              
172             sub setup {
173             # warn sprintf "CgiS::setup(%s) called ...\n", join('|',@_);
174 0     0 1   my $self = shift;
175              
176             # Establish default values for options
177              
178             # Options governing how this module is used
179 0   0       $self->config->{session}->{rewrite} ||= 0;
180              
181             # Options governing how CGI::Session is used
182 0   0       $self->config->{session}->{expires} ||= $DEFAULT_EXPIRATION_TIME;
183 0   0       $self->config->{session}->{cgis_dsn} ||= $DEFAULT_CGI_SESSION_DSN;
184 0   0       $self->config->{session}->{cgis_options} ||= $DEFAULT_CGI_SESSION_OPTIONS;
185              
186             # Options governing how module and CGI::Session interact
187              
188             # Note that we do not default the cookie-related configuration
189             # options here, but simpy test for presence
190              
191 0           return $self->NEXT::setup(@_);
192             }
193              
194              
195              
196             # Called by engines after prepare_cookies() and prepare_path()
197              
198             # This method attempts to locate a session id two different ways.
199             #
200             # First it checks whether a session id has been embedded within the
201             # request URL path. This is signaled by the sequence '/-/' followed
202             # by
203             # *any* characters and *any* number of them ?? Can we at least
204             # eliminate path separators? Would it be impractical to limit
205             # the possible characters to alphanums, like from MD5?
206             # Hmm, Digest docs say binary/hex/base64
207             # hex '0'..'9' and 'a'..'f' md5=32
208             # base64 65-character subset ([A-Za-z0-9+/=]) of US-ASCII is used.
209             # base64 'A'..'Z', 'a'..'z', '0'..'9', '+' and '/'. md5=22
210             # So far everyone uses hexdigest or decimal numbers
211             # a session id. If detected, then the request path is truncated
212             # to only the part preceding the session id marker '/-/'. The id
213             # value is used to set $c->sessionid()
214             #
215             # The second method is to check for a cookie sent in the request that
216             # has the name 'session'. If found then the cookie value is used to
217             # set $c->sessionid()
218              
219             sub prepare_action {
220 0     0 1   my $c = shift;
221              
222             # Try to extract a session id value embedded in request URL
223             # if ( $c->request->path =~ /^(.*)\/\-\/(.+)$/ ) {
224             # if ( $c->request->path =~ /^(.*)\/\-\/([^/]+)$/ ) {
225 0 0         if ( $c->request->path =~ /^(.*)\/\-\/([0-9a-f]+)$/ ) {
226 0           $c->request->path($1);
227 0           $c->sessionid($2);
228 0 0         $c->log->debug(qq/Found sessionid "$2" in path/) if $c->debug;
229             }
230             # XXX Shouldn't all of the above be conditonal on {rewrite} ?
231              
232 0 0         if ( my $cookie = $c->request->cookies->{session} ) {
233 0           my $sid = $cookie->value;
234 0           $c->sessionid($sid);
235 0 0         $c->log->debug(qq/Found sessionid "$sid" in cookie/) if $c->debug;
236             }
237              
238 0           $c->NEXT::prepare_action(@_);
239             }
240              
241              
242              
243             # Return ref to a session data hash that the caller can use as they want.
244              
245             sub session {
246 0     0 1   my ( $c ) = @_;
247             # warn sprintf "CgiS::session(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
248              
249 0 0         return if ! $c->_cgi_session_created;
250              
251             # if ( my $cgis_dump = $c->session_dump(1) ) {
252             # $c->log->debug( $cgis_dump );
253             # }
254              
255 0           return $c->{_session_cgis}->{session_data};
256             }
257              
258              
259              
260             sub _cgi_session_created {
261 0     0     my ( $c ) = @_;
262             # warn sprintf "CgiS::_cgi_session_created(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
263              
264 0           my $cgisess;
265              
266             # Is session area already present?
267 0 0 0       if ( defined $c->{_session_cgis}
268             && defined $c->{_session_cgis}->{cgisess} ) {
269 0           $cgisess = $c->{_session_cgis}->{cgisess};
270             }
271             else {
272             # We need to create the CGI::Session object and data. If the
273             # session is continued, we should find the sessiond id from
274             # request cookie or path (see prepare_action).
275 0           my $sid = $c->sessionid;
276 0           $cgisess = $c->_cgi_session_object_new( $sid );
277             }
278              
279 0           return $cgisess;
280             }
281              
282              
283             sub _cgi_session_object_new {
284 0     0     my ( $c, $id_or_query ) = @_;
285              
286             # CGI::Session expects either a session id string or a "query object"
287             # as the second argument in the call to new().
288             #
289             # For continued sessions we will have found the session id in cookie
290             # or path and put it in the $id_or_query parameter.
291             #
292             # But if this is the first request, we don't have anything to give
293             # CGI::Session. This is very bad as CGI::Session might try to do a
294             # call to CGI->new() out of a desire to resolve the lack of knowledge.
295             #
296             # We can supply a "query object" by giving CGIS the $c->request ref.
297             # But CGIS will use its default session parameter name 'CGISESSID'
298             # to poll ->cookie() and ->param() for values. If a user request
299             # should happen to have a form/URL parameter named 'CGISESSID' ? XXX
300              
301             # Default the "id or query" parameter to our request object
302 0 0         if ( ! defined $id_or_query ) {
303 0           $id_or_query
304             = Catalyst::Plugin::Session::CGISession::dummy_query->new();
305             }
306              
307 0           my $cgisess = $c->{session} = CGI::Session->new(
308             $c->config->{session}->{cgis_dsn},
309             $id_or_query,
310             $c->config->{session}->{cgis_options},
311             );
312              
313 0 0         if ( ! defined $cgisess ) {
314 0           $c->log->error( "Unable to create CGI::Session object, "
315             . "error message was: '" . CGI::Session->errstr() . "'" );
316 0           return undef;
317             }
318              
319             # If a session object expiration time was set in the configuration,
320             # ask CGI::Session to honor the time limit by setting expire time
321             # on the whole session.
322 0           my $expires = $c->config->{session}->{expires};
323 0 0         if ( $expires ) {
324 0           $cgisess->expire( $expires );
325             }
326              
327             # Start building our session-related data area
328              
329             # Save ref to CGI::Session object
330 0           $c->{_session_cgis}->{cgisess} = $cgisess;
331              
332             # C::P::Session::FastMmap has established a convention that there is
333             # one public session 'stash', a hash available to callers of ->session().
334             # CGIS is closer to the CGI.pm param() method and allows access to
335             # individually named parameters.
336             # We will emulate the C::P::S::F method by using one CGIS parameter
337             # named '_catalyst_session'
338              
339 0           my $session_data_ref;
340              
341             # If this session is new, we need to initialize the session data
342             # parameter to a created anonymous hash.
343 0 0         if( $cgisess->is_new() ) {
344 0           $session_data_ref = {};
345             }
346             else {
347             # Restore the hash ref from session entry's persistent data
348 0           $session_data_ref = $cgisess->param($SESSION_DATA_PARAMETER_NAME);
349 0 0         if ( ! defined $session_data_ref ) {
350 0           $session_data_ref = {};
351             }
352             }
353              
354             # We will make this hash ref available to callers, and save the
355             # entire hash at finalize()
356 0           $c->{_session_cgis}->{session_data} = $session_data_ref;
357 0           $cgisess->param( $SESSION_DATA_PARAMETER_NAME => $session_data_ref );
358              
359             # Set the visible session id from the CGI::Session object, in case
360             # the value was just created by CGIS
361 0           $c->sessionid( $cgisess->id );
362              
363 0 0         if( $cgisess->is_new() ) {
364 0           $c->log->debug( q{Created session '} . $c->sessionid . q{'});
365             }
366             else {
367 0           $c->log->debug( q{Retrieved session '} . $c->sessionid . q{'});
368             }
369              
370 0           return $cgisess;
371             }
372              
373              
374              
375             sub _cgi_session_object_close {
376 0     0     my ( $c ) = @_;
377              
378 0           my $cgisess = $c->{_session_cgis}->{cgisess};
379 0 0         croak "Missing CGI::Session object" unless defined $cgisess;
380             # warn sprintf "CgiS::_cgi_session_object_close() found CGI::S object %s\n", $cgisess;
381              
382 0           my $session_data_ref = $c->{_session_cgis}->{session_data};
383              
384             # if ( my $cgis_dump = $c->session_dump(1) ) {
385             # $c->log->debug( $cgis_dump );
386             # }
387              
388 0           my $dump_requested_type = $c->{_session_cgis}->{session_dump_request};
389 0 0 0       if ( $dump_requested_type
390             && $c->debug ) {
391 0           my $cgis_dump = $c->session_dump( $dump_requested_type );
392 0 0         if ( defined $cgis_dump ) {
393 0           $c->log->debug( $cgis_dump );
394             }
395             }
396              
397             # The latest CGI::Session release suggests a small difference in
398             # the best way to close session objects and write data to storage
399 0 0         if ( $CGI::Session::VERSION >= 4 ) {
400 0           $cgisess->flush;
401             }
402             else {
403 0           $cgisess->close;
404             }
405             }
406              
407              
408             # There are two places that the current session id value is stored, one
409             # using our accessor sessionid() and another residing in the CGI::Session
410             # object. We'll trust the CGI::Session copy and work with that.
411              
412             # But this is strange - we need to define which of these is most correct,
413             # lest some code use ->sessionid, such as the redirect-rewrite code below.
414              
415             sub finalize {
416 0     0 1   my ( $c ) = @_;
417             # warn sprintf "CgiS::finalize(%s) called ...\n", join('|',@_);
418              
419             # If the rewrite feature is enabled, update redirect URL when defined
420             # using ->sessionid
421 0 0 0       if ( $c->config->{session}->{rewrite}
      0        
422             && $c->sessionid
423             && $c->response->redirect ) {
424 0           my $redirect = $c->response->redirect;
425 0           $c->response->redirect( $c->uri($redirect) );
426             }
427              
428 0 0 0       if ( defined $c->{_session_cgis}
429             && defined $c->{_session_cgis}->{session_data} ) {
430              
431 0           my $cgisess = $c->{_session_cgis}->{cgisess};
432 0 0         croak "Missing CGI::Session object" unless defined $cgisess;
433              
434             # Grab the session id before closing the CGIS object
435 0           my $sid = $cgisess->id;
436              
437 0           $c->_cgi_session_object_close();
438              
439             # The CGI::Session expiration time is reset on every session
440             # access. We'll apply the same logic to cookies, updating
441             # on every request
442              
443             # Build the session id cookie. We always include the
444             # session id and expires values.
445 0           my %cookie = (
446             value => $sid,
447             expires => '+' . $c->config->{session}->{expires} . 's'
448             );
449              
450             # If there are cookie-specific configuration values to add
451             # to cookie. Note that config 'expires' can override default.
452 0           foreach my $option ( qw{ expires domain path secure } ) {
453 0           my $value = $c->config->{session}->{"cookie_$option"};
454 0 0         if ( defined $value ) {
455 0           $cookie{$option} = $value;
456             }
457             }
458             # This cookie will be sent in response
459 0           $c->response->cookies->{session} = \%cookie;
460              
461             # If rewrite was configured, update every URL in body text
462 0 0 0       if ( $c->config->{session}->{rewrite}
      0        
      0        
463             && $c->sessionid
464             && defined $c->res->body
465             && length $c->res->body ) {
466             my $finder = URI::Find->new(
467             sub {
468 0     0     my ( $uri, $orig ) = @_;
469 0           my $base = $c->request->base;
470 0 0         return $orig unless $orig =~ /^$base/;
471 0 0         return $orig if $uri->path =~ /\/-\//;
472 0           return $c->uri($orig);
473             }
474 0           );
475 0           $finder->find( \$c->res->{body} );
476             }
477             }
478              
479 0           return $c->NEXT::finalize(@_);
480             }
481              
482              
483              
484              
485             sub uri {
486 0     0 1   my ( $c, $uri ) = @_;
487 0 0         if ( my $sid = $c->sessionid ) {
488 0           $uri = URI->new($uri);
489 0           my $path = $uri->path;
490 0 0         $path .= '/' unless $path =~ /\/$/;
491 0           $uri->path( $path . "-/$sid" );
492 0           return $uri->as_string;
493             }
494 0           return $uri;
495             }
496              
497              
498             # - - - - - - - - - - - - - - - - - - - - - - - -
499             # Additional pass-through APIs for specialized CGI::Session features
500              
501             sub session_param {
502 0     0 1   my $c = shift;
503             # warn sprintf "CgiS::param(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
504              
505 0           my $cgisess = $c->_cgi_session_created;
506 0 0         return if ! defined $cgisess;
507              
508 0           return $cgisess->param(@_);
509             }
510              
511              
512             sub session_expire {
513 0     0 1   my $c = shift;
514             # warn sprintf "CgiS::expire(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
515              
516 0           my $cgisess = $c->_cgi_session_created;
517 0 0         return if ! defined $cgisess;
518              
519 0           return $cgisess->expire(@_);
520             }
521              
522              
523             sub session_flush {
524 0     0 1   my ( $c ) = @_;
525             # warn sprintf "CgiS::flush(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
526              
527 0           my $cgisess = $c->_cgi_session_created;
528 0 0         return if ! defined $cgisess;
529              
530 0           return $cgisess->flush();
531             }
532              
533              
534             sub session_delete {
535 0     0 1   my ( $c ) = @_;
536             # warn sprintf "CgiS::delete(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
537              
538 0           my $cgisess = $c->_cgi_session_created;
539 0 0         return if ! defined $cgisess;
540              
541 0           return $cgisess->delete();
542             }
543              
544              
545             sub session_is_new {
546 0     0 1   my ( $c ) = @_;
547             # warn sprintf "CgiS::is_new(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
548              
549 0           my $cgisess = $c->_cgi_session_created;
550 0 0         return if ! defined $cgisess;
551              
552 0           return $cgisess->is_new();
553             }
554              
555              
556              
557             sub session_dump {
558 0     0 1   my ( $c, $type ) = @_;
559             # warn sprintf "CgiS::session_dump(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
560              
561 0 0         return if ! defined $c->{_session_cgis};
562 0           my $cgisess = $c->{_session_cgis}->{cgisess};
563 0 0         return if ! defined $cgisess;
564              
565 0           my $cgis_dump;
566 0 0         if ( $type == SESSION_DUMP_DATA ) {
    0          
    0          
567 0           my $data_ref = $c->{_session_cgis}->{session_data};
568 0           $cgis_dump = Data::Dumper->Dump([ $data_ref ],['*cgis_session_data']);
569             }
570             elsif ( $type == SESSION_DUMP_PARAMS ) {
571 0           my $data_ref = $cgisess->dataref();
572 0           $cgis_dump = Data::Dumper->Dump([ $data_ref ],['*cgis_data']);
573             }
574             elsif ( $type == SESSION_DUMP_DATA ) {
575 0           $cgis_dump = $cgisess->dump();
576             }
577              
578 0           return $cgis_dump;
579             }
580              
581              
582             sub session_dump_at_close {
583 0     0 1   my ( $c, $type ) = @_;
584             # warn sprintf "CgiS::dump_at_close(%s) called from (%s,%s)\n", join('|',@_), ( caller() )[1,2];
585              
586 0 0         return if ! defined $c->{_session_cgis};
587              
588 0 0 0       if ( defined $type
      0        
589             && $type >= SESSION_DUMP_DATA
590             && $type <= SESSION_DUMP_SESSION ) {
591 0           $c->{_session_cgis}->{session_dump_request} = $type;
592             }
593             # XXX should we allow a value that "turns off" the current value?
594              
595             # Return the current setting
596 0           return $c->{_session_cgis}->{session_dump_request};
597             }
598              
599              
600             # - - - - - - - - - - - - - - - - - - - - - - - -
601              
602              
603             1; # Magic true value required at end of module
604             __END__
605              
606              
607             # We need to check under what conditions CGIS might try to access CGI
608             # parameters by itself.
609             # - query()
610             # uses query()
611             # - header() aka http_header()
612             # - cookie()
613             # - save_param()
614             # - load_param()
615             # - load() <<== whoa! We need to prevent this!
616             # Do we need to supply a query object to CGIS from $c->request ????
617             # See internal object param _QUERY, set from load() from new()
618             # We can supply a "query object" by giving CGIS the $c->request ref.
619             # But CGIS will use its default session parameter name 'CGISESSID'
620             # to poll ->cookie() and ->param() for values. If a user request
621             # should happen to have a form/URL parameter named 'CGISESSID' ? XXX
622             #
623             # I could create a dummy package and bless an object into it, that
624             # simply returns undef for every call to ->param() or ->cookie() ?
625              
626              
627             # - - - - - - - - - - - - - - - - - - - - - - - -
628              
629              
630             =head1 NAME
631              
632             Catalyst::Plugin::Session::CGISession - use CGI::Session for persistent session data
633              
634              
635             =head1 VERSION
636              
637             This document describes Catalyst::Plugin::Session::CGISession version 0.0.1
638              
639              
640             =head1 SYNOPSIS
641              
642             use Catalyst qw{ ... Session::CGISession ... };
643              
644             MyApp->config->{session} = {
645             expires => 3600,
646             rewrite => 1,
647             };
648              
649             $c->session->{user_email} = 'quibble@dibble.edu';
650              
651             # Later, in another following request:
652              
653             $smtp->to( $c->session->{user_email} );
654              
655              
656             =head1 DESCRIPTION
657              
658             =for author to fill in:
659             Write a full description of the module and its features here.
660             Use subsections (=head2, =head3) as appropriate.
661              
662             This plugin provides the same functionality as the original
663             L<Session::FastMmap|Catalyst::Plugin::Session::FastMmap> plugin but uses the
664             L<CGI::Session|CGI::Session> module for the session data management.
665              
666             The motivations to develop this plugin were:
667              
668             =over 4
669              
670             =item *
671             provide better session data expiration handling, as is
672             available through the CGI::Session module
673              
674              
675             =item *
676             provide an easier migration to Catalyst for applications that
677             have been using CGI::Session and its param() and other methods
678              
679              
680             =item *
681             allow Windows users to avoid the workarounds needed to make
682             Cache::FastMmap work
683              
684             =back
685              
686             The difference in session expiration between this plugin and
687             C<Session::FastMmap>
688             is small but important. CGI::Session resets the expiration time limit
689             on every access to the session. A one day time limit means the session
690             data disappears 24 hours after the I<last> request using that session.
691             With Session::FastMmap the limit would be 24 hours after the I<first>
692             request, when the session is created.
693              
694             While this plugin adds some functions and methods beyond those available
695             with C<Session::FastMmap>,
696             new development most likely should avoid using these features.
697             Try to use only the common feature, L<session()|/session>,
698             to stay compatible with C<Session::FastMmap>
699             and other future session plugins.
700              
701              
702             =head1 INTERFACE
703              
704             =head2 PUBLIC METHODS
705              
706             =head3 session
707              
708             Returns a hash reference that the caller can use to store persistent
709             data items.
710             Everything stored into this hash will be saved to storage when a
711             request completes. Upon the next request with the same session id
712             the saved data will again be available through this method.
713              
714             This method performs the same functions as Session::FastMmap::session.
715              
716             =head3 uri
717              
718             Extends an uri with session id if needed.
719             This is used when the C<{rewrite}> configuration option is enabled.
720              
721             my $uri = $c->uri('http://localhost/foo');
722              
723             This method performs the same functions as Session::FastMmap::uri.
724              
725              
726             =head2 EXPOSED CGI::SESSION METHODS
727              
728             Applications might require some of the specialized features of CGI:Session.
729             A small number of CGI::Session methods are exposed through this plugin.
730              
731              
732             =head3 session_param
733              
734             A single session data hash may be too restrictive for some applications.
735             In particular, some applications may want to expire individual data items
736             separately, as is allowed by CGI::Session. See the CGI::Session
737             L<C<param()>|CGI::Session/"param"> method documentation for more details.
738              
739              
740             =head3 session_expire
741              
742             Setting a data item-specific expiration time is done with the CGI::Session
743             L<expire()|CGI::Session/expire> method. Please see that documentation
744             for details.
745              
746             =head3 session_is_new
747              
748             It may be useful for applications to know when a session is newly created
749             and not a continuation of a previous session. This is usually detectable
750             by checking for missing previous values. But if an application really has
751             to know, the CGI::Session L<is_new()|CGI::Session/is_new> method
752             will tell you. Please see that documentation for details.
753              
754             =head3 session_flush
755              
756             The persistent session data hash is written to backing storage at the end
757             of every request. If for some reason an application needs to force an
758             update early, this method will call the
759             CGI::Session L<flush()|CGI::Session/flush> method.
760              
761             =head3 session_delete
762              
763             Calls the CGI:Session L<delete()|CGI::Session/delete> method which marks
764             the session as "to be deleted." Note that the session data is not actually
765             deleted from storage until the current request finishes, or if you
766             explicitly call C<session_flush()>.
767              
768             =head3 session_dump
769              
770             =head3 session_dump_at_close
771              
772             CGI::Session provides a L<dump()|CGI::Session/dump> method as a
773             convenience during testing. This plugin extends that method to dump a
774             varying amount of data and also postponing the request, dumping the data
775             at end of request into the debug log.
776              
777             C<session_dump()> will immediately return a string of formatted dump data.
778             C<session_dump_at_close()> will wait until end of request processing and
779             then dump the session data into the debug log just before the data is
780             written to backing storage.
781              
782             You may specify how much data to dump using a single number value:
783              
784             =over 4
785              
786             =item * =1 dump the session data hash returned by L< C<session()>|/session>
787              
788             =item * =2 dump the whole CGI::Session parameters hash, including
789             parameters set using L< C<session_param()>|/session_param>
790              
791             =item * =3 dump the entire CGI::Session object
792              
793             =back
794              
795             my $dumped_hash_string = $c->session_dump(1);
796              
797             will return a string containing the Data::Dumper formatted dump of the
798             session hash.
799              
800             $c->session_dump_at_close(2);
801              
802             specifies that at end of request processing, the usual session data hash and
803             also any other parameters should be displayed in the Catalyst debug log.
804              
805              
806             =head2 EXTENDED CATALYST METHODS
807              
808             =head3 setup
809              
810             Check session-related configuration values and default those not
811             set from the configuration.
812              
813              
814             =head3 prepare_action
815              
816             This method attempts to determine the session id for the current request in
817             two different ways.
818              
819             First it checks whether a session id has been embedded within the
820             request URL path. This is signaled by the sequence C<'/-/'> followed
821             by
822             a session id. If this is found then the request path is truncated
823             to only the part preceding the session id marker C<'/-/'>. The part
824             following the marker is used to set C<$c-E<gt>sessionid()>
825              
826             The second method is to check for a cookie with the name 'session'
827             sent in the request. If found then the cookie value is used to
828             set C<$c-E<gt>sessionid()>.
829              
830             If a session id is found by both methods the value from the cookie
831             will be used.
832              
833             =head3 finalize
834              
835             This method is called as part of the end of request processing chain.
836              
837             If session data has been created or read then this method is responsible for
838             writing session data out to backing storage.
839              
840             If the C<rewrite> configuration option is enabled then URI rewriting
841             is also performed on body text and any redirect URL.
842              
843              
844             =head1 CONFIGURATION AND ENVIRONMENT
845              
846             Session::CGISession uses configuration options as
847             found in C<$c-E<gt>config-E<gt>{session}> data.
848              
849             =head2 CONFIG OPTIONS FOR MODULE
850              
851             =head3 expires
852              
853             How many seconds until the session expires. The default is 24 hours.
854              
855             Note that the underlying CGI::Session handler resets the session expiration
856             time upon every access. Thus a session will not normally expire until this
857             many seconds have elapsed since the I<last> access to the session. This is
858             a useful difference from the Session::FastMmap plugin which sets the
859             expiration time of a session only once at session creation.
860              
861              
862             =head3 rewrite
863              
864             One method for remembering the current session id value from one request
865             to the next is to embed the session id into every request URL. If the
866             user has disabled cookies in their browser this is the only way to pass
867             session id from one request to another.
868              
869             When this option is enabled the module will attempt to add the session id
870             to every URL in the response output. In addition it will update a
871             redirect URL when redirect is used.
872              
873             See method C<uri()>
874              
875             This configuration option requests the same feature as Session::FastMmap provides.
876              
877             =head2 CONFIG OPTIONS FOR COOKIES
878              
879             =head3 cookie_expires
880              
881             how many seconds until the session cookie expires at the client browser.
882             See expires option in L<CGI::Cookie|CGI::Cookie> for format.
883             default is the expires option described above. This option will
884             override the default when specified.
885              
886             =head3 cookie_domain
887              
888             Domain set in the session cookie.
889             See domain option in L<CGI::Cookie|CGI::Cookie> for format.
890             default is none.
891              
892             =head3 cookie_path
893              
894             Path set in the session cookie.
895             See path option in L<CGI::Cookie|CGI::Cookie> for format.
896             default is none.
897              
898             =head3 cookie_secure
899              
900             Secure flag set in the session cookie.
901             See secure option in L<CGI::Cookie|CGI::Cookie> for format.
902             default is none.
903              
904              
905              
906             =head2 CONFIG OPTIONS FOR CGI::SESSION
907              
908             You may want to explicitly control how and where CGI::Session stores
909             session data files. While this module provides defaults
910             for parameters to CGI::Session, your needs may require specific values.
911              
912             You may specify values to be given directly to the CGI::Session C<new()>
913             method using the C<cgis_dsn> and C<cgis_options> configuration parameters.
914              
915             =head3 cgis_dsn
916              
917             This option value becomes the first argument to the CGI::Session
918             L<C<new()>|CGI::Session/new> call, C<$dsn> or B<Data Source Name>. This parameter
919             can configure the backing storage type, the method for serializing data,
920             and the method for creating session id values. It is a combination
921             of one, two or three specifications.
922              
923             The default value used by this module is:
924              
925             $c->config->{session}->{cgis_dsn}
926             = 'driver:File;serializer:Storable;id:MD5';
927              
928              
929             =head3 cgis_options
930              
931             Some of the driver, serializer and id generation modules used with
932             CGI::Session can be given additional parameters to control how they
933             work. One obvious example is telling the plain file driver what
934             directory to use when storing session files.
935              
936             You may use this hash value to supply these additional parameters,
937             given to CGI::Session C<new()> as the third argument.
938             These are named parameters and so you must use a hash reference.
939             An example in code would be:
940              
941             $c->config->{session}->{cgis_options}
942             = {
943             DataSource => 'dbi:mysql:database=warren;host=rabitton',
944             User => 'flopsie',
945             Password => 'furryface',
946             };
947              
948             An example in the form of a section from a YAML file would be:
949              
950             session:
951             cgis_dsn: driver:mysql;serializer:Storable;id:MD5
952             cgis_options:
953             DataSource: dbi:mysql:database=warren;host=rabitton
954             User: flopsie
955             Password: furryface
956              
957              
958             Details about the various parameters for drivers and id generation
959             modules can be found in the L<CGI::Session|CGI::Session/distribution> documentation.
960              
961             Database driver modules support the following parameters:
962              
963             DataSource - the DSN value given to DBI->connect()
964              
965             Handle - a DBI database handle object ($dbh), if already connected
966              
967             TableName - name of the table where session data will be stored
968              
969             User - user privileged to connect to the database defined in DataSource
970              
971             Password - password of the same user
972              
973             Individual drivers support other parameters, such as:
974              
975             file Directory where session files will be stored
976              
977             db_file FileName location of the Berkely DB file
978              
979             postgresql ColumnType value 'binary' might be needed
980              
981              
982             The default value used by this module (to match the above default
983             for cgis_dsn) is:
984              
985              
986             $c->config->{session}->{cgis_options}
987             = {
988             Directory => File::Spec->tmpdir()
989             };
990              
991              
992             =head2 SPECIALIZED OPTIONS FOR CGI::SESSION
993              
994             CGI::Session has some settings which are not easily specified using
995             the available API calls. Unfortunately a number of these can be set
996             only by storing into global variables.
997              
998             If you need to change the default values of these CGI::Session settings
999             you will have to manage to do this in your code, before this plugin module
1000             is called by the C<MyApp-E<gt>setup()> call.
1001              
1002             An example will illustrate this. Suppose that the default values for
1003             C<cgis_dsn> and C<cgis_dsn> are satisfactory, but you want to change
1004             the naming of session files created by the default CGI::Session File
1005             driver. That driver's default for filenames is C<'cgisess_%s'> and you
1006             would rather use C<'myapp_%s.ses'>.
1007              
1008              
1009             use CGI::Session::Driver::file;
1010             use Catalyst qw{ ... Session::CGISession ... };
1011              
1012             __PACKAGE__->config->{session} = {
1013             expires => 7 * 24 * 60 * 60,
1014             rewrite => 1,
1015             };
1016              
1017             $CGI::Session::Driver::file::FileName = 'myapp_%s.ses';
1018              
1019             __PACKAGE__->setup();
1020              
1021             The plugin module is not initialized until the call to C<setup()>.
1022             Any prior modifications to the defaults of CGI::Session will be
1023             available to the plugin.
1024              
1025             If you are using CGI::Session 3.x you would have to code:
1026              
1027             use CGI::Session::File;
1028              
1029             $CGI::Session::Driver::FileName = 'myapp_%s.ses';
1030              
1031              
1032             =for great knowledge
1033             $s = new CGI::Session("driver:file", $sid, {Directory=>'/tmp'});
1034             Naming conventions of session files are defined by
1035             $CGI::Session::Driver::file::FileName global variable. Default value of this
1036             variable is cgisess_%s, where %s will be replaced with respective session ID.
1037             Should you wish to set your own FileName template, do so before requesting for
1038             session object:
1039             $CGI::Session::Driver::file::FileName = "%s.dat";
1040             $s = new CGI::Session();
1041             For backwards compatibility with 3.x, you can also use the variable name
1042             $CGI::Session::File::FileName, which will override one above.
1043             DRIVER ARGUMENTS
1044             The only optional argument for file is Directory, which denotes location of the
1045             directory where session ids are to be kept. If Directory is not set, defaults
1046             to whatever File::Spec->tmpdir() returns. So all the three lines in th
1047             db_file
1048             $s = new CGI::Session("driver:db_file", $sid, {FileName=>'/tmp/cgisessions.db'});
1049             db_file stores session data in BerkelyDB file using DB_File - Perl module. All
1050             sessions will be stored in a single file, specified in FileName driver argument
1051             as in the above example. If FileName isn't given, defaults to
1052             mysql
1053             $s = new CGI::Session("driver:mysql", undef, {
1054             TableName=>'my_sessions',
1055             DataSource=>'dbi:mysql:shopping_cart'});
1056             $s = new CGI::Session( "driver:mysql", $sid, { Handle => $dbh } );
1057             ^^^^^^
1058             postgresql
1059             $session = new CGI::Session("driver:PostgreSQL", undef,
1060             {Handle=>$dbh, ColumnType=>"binary"});
1061             ^^^^^^ ^^^^^^^^^^
1062             sqlite
1063             $s = new CGI::Session("driver:sqlite", undef, {TableName=>'my_sessions'});
1064             DataSource should be in the form of dbi:SQLite:dbname=/path/to/db.sqlt.
1065             $s = new CGI::Session("driver:sqlite", $sid, {DataSource=>'/tmp/sessions.sqlt'});
1066             $s = new CGI::Session("driver:sqlite", $sid, {Handle=>$dbh});
1067             all database drivers
1068             Following driver arguments are supported:
1069             DataSource - First argument to be passed to DBI->connect().
1070             User - User privileged to connect to the database defined in DataSource.
1071             Password - Password of the User privileged to connect to the database defined in DataSource
1072             Handle - To set existing database handle object ($dbh) returned by DBI->connect(). Handle will override all the above arguments, if any present.
1073             TableName - Name of the table session data will be stored in.
1074              
1075              
1076             =head1 DIAGNOSTICS
1077              
1078             =for author to fill in:
1079             List every single error and warning message that the module can
1080             generate (even the ones that will "never happen"), with a full
1081             explanation of each problem, one or more likely causes, and any
1082             suggested remedies.
1083              
1084             There are conditions where CGI::Session will be unable to create a
1085             session object. The most likely causes are misconfigured options or
1086             unavailable modules.
1087              
1088             When CGI::Session returns an error the error message will be repeated in
1089             the Catalyst error log. Below is an example error message resulting
1090             from a misspelled name in the C<cgis_dsn> configuration parameter:
1091              
1092             [Thu ... 2005] [catalyst] [e]
1093             Unable to create CGI::Session object, error:
1094             'new(): failed: couldn't load CGI::Session::Serialize::storrable:
1095             Can't locate CGI/Session/Serialize/storrable.pm in @INC
1096              
1097             Please note that CGI::Session 3.x DSN names are case-sensitive.
1098             While "driver:mysql" works under CGIS 4.x, it must be "driver:MySQL"
1099             when using CGIS 3.x.
1100              
1101              
1102             =head1 DEPENDENCIES
1103              
1104             =for author to fill in:
1105             A list of all the other modules that this module relies upon,
1106             including any restrictions on versions, and an indication whether
1107             the module is part of the standard Perl distribution, part of the
1108             module's distribution, or must be installed separately. ]
1109              
1110             This module was developed using the first CGI::Session 4.00 release.
1111             It was subsequently tested under 3.95, the last 3.x version.
1112              
1113             Testing has been done using:
1114              
1115             Windows XP
1116              
1117             CGI::Session 4.00 'File' driver
1118              
1119             CGI::Session 4.00 MySQL 3.23.x
1120              
1121             CGI::Session 3.95 MySQL 3.23.x
1122             Note: driver name case sensitivity, e.g.
1123             cgis_dsn: driver:MySQL;serializer:Storable;id:MD5
1124             Note: TableName not available, must use global variable, e.g.
1125             $CGI::Session::MySQL::TABLE_NAME = 'myapp_sessions';
1126              
1127             CGI::Session 3.95 'File' driver
1128             Note: different global variable $CGI::Session::File::FileName
1129              
1130             Linux
1131              
1132             CGI::Session 3.95 'File' driver
1133             Note: this driver leaves session data tainted
1134              
1135             CGI::Session 3.95 MySQL 3.23.x
1136              
1137             =for documentor:
1138             Planned:
1139             CGI::Session 4.00 'File' driver
1140             CGI::Session 4.00 MySQL 3.23.x
1141              
1142              
1143             =head1 INCOMPATIBILITIES
1144              
1145             =for author to fill in:
1146             A list of any modules that this module cannot be used in conjunction
1147             with. This may be due to name conflicts in the interface, or
1148             competition for system or program resources, or due to internal
1149             limitations of Perl (for example, many modules that use source code
1150             filters are mutually incompatible).
1151              
1152             None reported.
1153              
1154              
1155             =head1 BUGS AND LIMITATIONS
1156              
1157             =for author to fill in:
1158             A list of known problems with the module, together with some
1159             indication Whether they are likely to be fixed in an upcoming
1160             release. Also a list of restrictions on the features the module
1161             does provide: data types that cannot be handled, performance issues
1162             and the circumstances in which they may arise, practical
1163             limitations on the size of data sets, special cases that are not
1164             (yet) handled, etc.
1165              
1166             No bugs have yet been reported.
1167              
1168             Please report any bugs or feature requests to
1169             C<bug-catalyst-plugin-session-cgisession@rt.cpan.org>, or through the web interface at
1170             L<http://rt.cpan.org>.
1171              
1172             =head2 Catalyst Plugin Module Order
1173              
1174             Other Catalyst plugin modules may rely upon session data in order to
1175             correctly initialize themselves. This may require some care in the
1176             order that plugin modules are named to Catalyst.
1177              
1178             For instance, the C::P::Authentication::CDBI module expects to find
1179             C<$c-E<gt>session-E<gt>{user}> and C<$c-E<gt>session-E<gt>{user_id}>
1180             from any previous session for logged-in users.
1181              
1182             Thus when defining the order of plugins you should take care that the
1183             session modules like C::P::Session::CGISession are loaded before any
1184             module that might need session data.
1185              
1186             use Catalyst qw{ ...
1187             Session::CGISession
1188             Authentication::CDBI
1189             ...
1190             };
1191              
1192             =head1 SEE ALSO
1193              
1194             =over 4
1195              
1196             =item L<Catalyst|Catalyst>
1197              
1198             =item L<Catalyst::Plugin::Session::FastMmap|Catalyst::Plugin::Session::FastMmap>
1199              
1200             =item L<Catalyst::Plugin::Session::Flex|Catalyst::Plugin::Session::Flex>
1201              
1202             =item L<CGI::Session|CGI::Session>
1203              
1204             =item L<CGI::Cookie|CGI::Cookie>
1205              
1206             =back
1207              
1208             =head1 THANKS
1209              
1210             To Christian Hansen, from whose test program implementation of
1211             CGI::Session use I borrowed extensively,
1212              
1213             To Andy Grundman, for the solution to poking cookie values,
1214              
1215             To Sebastian Riedel and Marcus Ramberg, for the
1216             Catalyst::Plugin::Session::FastMmap module used to get me started,
1217              
1218             And to them and the rest of the contributors to Catalyst, for a great start!
1219              
1220              
1221             =head1 AUTHOR
1222              
1223             Thomas L. Shinnick C<< <tshinnic@cpan.org> >>
1224              
1225              
1226             =head1 LICENCE AND COPYRIGHT
1227              
1228             Copyright (c) 2005, Thomas L. Shinnick C<< <tshinnic@cpan.org> >>. All rights reserved.
1229              
1230             This module is free software; you can redistribute it and/or
1231             modify it under the same terms as Perl itself. See L<perlartistic>.
1232              
1233              
1234             =head1 DISCLAIMER OF WARRANTY
1235              
1236             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
1237             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
1238             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
1239             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
1240             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1241             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
1242             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
1243             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
1244             NECESSARY SERVICING, REPAIR, OR CORRECTION.
1245              
1246             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
1247             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
1248             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
1249             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
1250             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
1251             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
1252             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
1253             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
1254             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
1255             SUCH DAMAGES.
1256              
1257             =cut
1258              
1259             # vim:ft=perl:ts=4:sw=4:et:is:hls:ss=10: