ABSTRACT

CGIsession.pm - extends CGI.pm with session tracking and session data storage using a cookie or URL-rewrite.

If the browser accepts cookies, a cookie will be used, otherwise session tracking will be guarantied by URL-rewrite (where each URL of your application will be extended by a path info, which is the sessionID). The application can force URL-rewrite. For REST or SOAP applications, there is the possibility to pass the sessionID with a CGI param.

Session tracking with cookies has the advantage that a user, who surfs outside of your application, is recognized right away, whenever he comes back to your application. On the other hand, he can not open two or more sessions simultaneous to your application. Cookies can be turned of in most browsers.

Your application should use the below described methods to

This methods in CGIsession contain code to ensure session tracking with URL-rewrite and/or a cookie.


SYNOPSIS

Only the object oriented interface to CGI is supported. Do not try to use the CGI.pm import facility.

 #!/usr/bin/perl -w
 use CGIsession;
 $cgi = CGIsession->connect( -application=>'MyShop' );
 ...
 print $cgi->header;
 print $cgi->start_html;
 ...
 my $counter = $cgi->session_data('counter');
 $counter++;
 $cgi->session_data(counter => $counter);
 print $cgi->h1("Call number $counter");
 ...
 print $cgi->link_to_script('', "Link to myself");
 print $cgi->startform;
 print $cgi->submit(-name=>'continue',
                    -value=>'continue this script');
 print $cgi->endform;
 ...
 print $cgi->link_to_script('other.cgi', "Link to other.cgi");
 print $cgi->startform(-action=>'script2.cgi');
 print $cgi->submit(-name=>'submit',
                    -value=>'Form will be processed by script2.cgi');
 print $cgi->endform;
 ...
 print $cgi->end_html;
 $cgi->save_session;


DESCRIPTION

Simple Application Interface

CGIsession.pm inherits from CGI.pm. Only differences to CGI.pm are described here. Refer to CGI documentation for CGI.pm. You can find CGI.pm in CPAN.

The Object Constructor

$cgi = CGIsession->connect( )
$cgi = CGIsession->connect( -application=>$apname )
$cgi = CGIsession->new( )
Constructor. Returns a handle to the CGIsession object, which includes all methods and functionality of CGI.pm.

new() is just an alias for connect(). Connect is a more descriptive name for the object constructor, because a web application reconnects to its sessions with each request.

The argument $apname is used for session tracking. You can give any name that would be a legal Perl name. All scripts on the same host with the same application name build an application: they have access to shared session data. If you call connect() without an -application argument, an application name will be constructed from the calling URL, including host, port and path to the script. Hence, by default, all scripts in a directory (accessed through the same virtual host) build an application. If you prefer an other arrangement of your scripts, all scripts which build an application should specify the same application name in their connect() method calls.

All non-word characters in the application name will be translated to an underlines.

Access to Session Storage

$old_value = $cgi->session_data( $key )
$old_value = $cgi->session_data( $key => $new_value )
Get/set a session variable named $key. The key and value should both be strings or be translated to strings by Perls print. So don't try any object references here. Just plain simple scalars. Any string of bytes will go.

$href = $cgi->session_data( )
If method session_data is called without arguments, it returns a reference to the internal hash where CGIsession stores the session data.

$old_value = $cgi->delete_session_data( $key )
Deletes the session variable identified by $key from session data and returns its old value.

@array = $cgi->session_data_keys( )
Returns a sorted array of all keys in session data.

$old_value = $cgi->private_data( $key )
$old_value = $cgi->private_data( $key => $new_value )
Get/set a private session variable named $key. The key and value should both be strings or be translated to strings by print. So don't try any references here. Just plain simple scalars. Private session variables are private to a script, which is identified by the path in the calling URL.

Use private_data for data that other scripts will not care about and session_data only for datas, which more than one script will access.

Private session data will be saved to a file in session storage. The filename of this savefile in session storage will be the same as the absolute path from the URL, but all non word characters are converted to underlines. So two scripts may share the the private data (ie. /cgi-bin/my-test.cgi and /cgi-bin/my.test.cgi would both access the private data _cgi_bin_my_test_cgi). To avoid name conflicts with CGIsession data files, non of your scripts should have an all uppercase name.

$href = $cgi->private_data( )
When calling method private_data without an argument, a reference to the internal hash is returned.

$old_value = $cgi->delete_private_data( $key )
Deletes the private session variable identified by $key from session data and returns its old value.

@array = $cgi->private_data_keys( )
Returns a sorted array of all keys in private data.

$cgi->save_session( )
This will write the session data to a permanent storage. It should be called at the end of every script. Otherwise changes on the session data will be lost. The $cgi->save_session() method should be called only once in each script. After a $cgi->save_session() call your script can not call $cgi->delete_session().

$cgi->delete_session( )
Deletes the session data of the current session (kind of a logout). It does not make sense to call method $cgi->save_session() after you deleted its session storage. If your script does it anyway, $cgi->save_session() will neither save nor raise an error (this is Perl after all). On the other hand, your script should not try to call $cgi->$delete_session() after $cgi->save_session().

Even after a delete_session call, the session_data and private_data work until the end of the current script.

If your script calls $cgi->delete_session() before writing the last page (before the $cgi->header() call). The $cgi->header() method will try to delete the cookie in the browser used for session tracking. If your script does not generate a page, but makes a $cgi->redirect(), the cookie will only be deleted, if the URL of the $cgi->redirect() is a relative or absolute URL (not a full URL beginning with http:// or https://).

Temporary Files for a Session

Your application may use the following methods to get directories to store temporary files.

$cgi->application_directory( )
Returns the absolute path to a directory (without terminating /). All scripts (of all sessions) of an application may read and write files in this directory. I.e. this directory is private to an application. It is in /tmp/CGIsessions/... and may get lost at system restart on a Solaris, but is never deleted by CGIsession.

You may use the methods $cgi->setlock(...) and $cgi->unlock(...) to avoid that concurrently running scripts mess files in that directory.

$cgi->session_directory( )
Returns the absolute path to a directory (without terminating /). All scripts of a session may read and write files in this directory. I.e. this directory is private to a session and will be deleted when by $cgi->delete_session(). Concurrent access to this directory is locked by CGIsession.

CGIsession overrides CGI methods for session tracking.

$cgi->header();
100% CGI compatible, but may add a cookie.

$cgi->redirect($url);
$cgi->redirect(-location=>$url ...)
The arguments of redirect() are exactly as described in CGI.pm with one exception: the URL should be relative or absolute, if you redirect to a script of the same application. Relative or absolute URLs will be translated to full URLs by CGIsession, but path_info or a cookie may be added for session tracking. For full URLs (beginning with http:// or https:// CGIsession will not add path_info or cookies for session tracking; hence the surfer may leave your application at this point.

$cgi->link_to_script( '', $visible_text )
$cgi->link_to_script( $script, $visible_text )
$cgi->link_to_script( $script, $visible_text, $query )
$cgi->link_to_script( $script, $visible_text, [...] )
$cgi->link_to_script( { href=>'...' }, $visible_text )
$cgi->link_to_script( { href=>'...' }, $visible_text, [...] )
Returns a link (<a href=...>...</a>-tag) to the specified URL, which should be a script of the same application.

The first argument may be a an URL as a string or a reference to a hash which contains attributes for the a-tag - one of which should be a href=>$url attribute which specifies an URL. If the first argument is an empty string or undef, the executing scripts URL will be used.

The URL may be relative, absolute or full. It will be translated to an full URL (including protocol, host and absolute path). The URL may contain a query string and/or anchor. Typically it is just the name of a script in the same directory.

The second argument is always required. It is the visible text of the link.

An optional third argument can be used to pass a query. This can be an URL-encoded string (including question mark, i.e. '?OS=Linux;fruit=apple') or a reference to an array of key value pairs (i.e. [OS => 'Linux', fruit => 'apple']). If URL (from the first argument) contains already a query string, the query from the third argument is prepended.

When session tracking is done by URL-rewrite, CGIsession will add the sessionID as path info into the URL.

$cgi->start_form();
$cgi->start_multipart_form();
$cgi->startform();
100% CGI compatible: the form tag is constructed by CGIs startform method, but modified for URL-rewrite. CGI contains a bug (a previous query string may be in the action attribute), which is fixed too. start_form is just a alias for startform (because I write it wrong most of the time).

Features for Professional Applications

CGIsession features support for application initialization scripts - doors to which a surfer must enter your application and/or support for user validation and security checks. There is even a deluxe method to clean up abandoned sessions; this one is called each time when a new session storage is allocated internally. But you may use it for cronjobs or supervisor scripts to manage sessions of all the surfers of your applications.

Browsers behave different when a surfer hits the ENTER key in a textfield of a form. For example Safari of Mac OSX will simulate a click on the first submit button in the same form, while MS-Explorer on the same system will send no submit buttons. CGIsession can simulate the behavior of Safari for all browsers.

An other concern may be resubmission of a form: the user clicks a submit button twice or a hacker uses the browsers back button to resubmit a login form with filled in username and password. CGIsession provides a method to generate a protected submit button. The script will be able to distinguish if the form was submitted the first time or if the submission was repeated.

There is also support for client IP address check. CGIsession can be configured to redirect a request whenever the client IP address changes.

Improved Handling of Submit Buttons

$cgi->submit();
100% CGI compatible argument list. CGIsession fixes a problem with different browser behavior when the surfer hits ENTER in a textfield. MS Internet Explorer would submit the form without any submit buttons, but Safari would submit the form with a simulated click on the first submit button in the form. It is just a matter of time, until a browser will submit the form with all the submit buttons in the form clicked.

CGIsession includes a hidden field with the submit button names in each form (constructed by endform() method). If more than one of this submit button names are included in a request, only the first is kept - all others are deleted. If none of them are in the request, the first is included.

This guaranties, that all browsers look like Safaris. Your scripts should use different names for each submit button in a form (which is a good thing to do anyway).

If a submit() has no -name=>... or no -value=>... argument, or the name or value contain a TAB (ASCII HT Perl \t), the submit button check feature is disabled for that form. You may also specify the argument -no_submit_check=>1 in the connect() method call to disable the check for missing or multiple submit buttons in the current request.

$cgi->protected_submit(-name=>$first_name, ...)
Returns a string for a submit button. The main difference to submit() is, that CGIsession will detect when a form with a protected_submit button is resubmitted (i.e. the surfer used the browsers back button and resubmits the form). This should be prevented for user validation pages or submissions of orders etc. CGIsession will generate a tag with a random name. This random name will be translated back by CGIsession when the form is submitted.

Example: try the following code:

    #/usr/bin/perl -w
    use CGIsession;
    $cgi = CGIsession->connect();
    print $cgi->header;
    print $cgi->start_html;
    if (defined $cgi->param('text')) {
        print $cgi->p("We know that the form was submitted, ",
                      "because there is the text field with '",
                      $cgi->param('text'),
                      "' in it.");
        if ($cgi->param('go')) {
            if ($cgi->param('go') eq 'noGo2') {
                print $cgi->p("<b><font color='red'>Repeated submission</font></b>",
                              " of button 'Go2'.");
            } else {
                print $cgi->p("<b><font color='green'>First submission</font></b>");
            }
            print $cgi->p("param(go) = " . $cgi->param('go'));
        } elsif ($cgi->param('nogo')) {
            print $cgi->p("<b><font color='red'>Repeated submission</font></b>",
                          " of button 'Go3' or 'Go4'.");
            print $cgi->p("param(nogo) = " . $cgi->param('nogo'));
        } else {
            print $cgi->p("<b><font color='red'>Repeated submission</font></b>",
                          " of 'Go1' or a ENTER in the text field.");
            print $cgi->p("There was no 'go' or 'nogo' param");
        }
        print $cgi->p("Use the BACK button of your browser to go back or ",
                      "RELOAD the page or use this link to ",
                      $cgi->link_to_script('', "restart this demo"));
    } else {
        print $cgi->startform();
        print $cgi->textfield(-name=>'text', -value=>'Text', -size=>20);
        print $cgi->br;
        print $cgi->protected_submit(-name=>'go',
                                     -value=>'Go1');
        print $cgi->protected_submit(-name=>'go',
                                     -value=>'Go2', -repeated_value=>'noGo2');
        print $cgi->protected_submit(-name=>'go', -repeated_name=>'nogo',
                                     -value=>'Go3');
        print $cgi->protected_submit(-name=>'go', -repeated_name=>'nogo',
                                     -value=>'Go4', -repeated_value=>'noGo4');
        print $cgi->endform();
    }
    print $cgi->end_html;
    $cgi->save_session;
$cgi->end_form();
$cgi->endform();
100% CGI compatible. But may add a hidden field.

Initialization: Doors to Your Application.

If every surfer should always enter your application at the main door, regardless of which URL he/she had bookmarked, let all scripts do two things:

  1. $cgi = CGIsession->connect( -session_init=>'maindoor.cgi' );
  2. Call the connect() method with -session_init => $url argument giving the main doors script URL. The URL should be relative or absolute, but not full (i.e. not begin with http:// or https://). Typically it is just the name of a script in the same directory.

  3. &process_request() unless $cgi->request_is_redirected( );
  4. If connect() created a new session storage, method request_is_redirected() will redirect the request to the door script specified as -session_init=>URL argument of connect(); request_is_redirected() will signal this to your script by returning a true value. If connect() reconnected to an existing session request_is_redirected() will return false to inform your script, that it should go on and handle the request.

    Just pack all your old main program in a subroutine and call it only if $cgi->request_is_redirected() returns false. It is good practice, to call $cgi->save_session(), even when your script does not handle the dialog.

Example: Application with a single entry 'door'.

    maindoor.cgi
    #!/usr/bin/perl -w
    use CGIsession;
    $cgi = CGIsession->connect();
    do_dialog() unless $cgi->request_is_redirected();
    $cgi->save_session();
    # ---
    sub do_dialog {
        # do whatever your first application script should do ...
    }

The maindoor.cgi script does not specify a session_init script, because it is that itself. The check unless $cgi->request_is_redirected() will always execute do_dialog(), but does not harm and will be useful later (see user validated applications below).

    any other script of your application
    #!/usr/bin/perl -w
    use CGIsession;
    $cgi = CGIsession->connect( -session_init => 'maindoor.cgi' );
    do_dialog() unless $cgi->request_is_redirected();
    $cgi->save_session();
    # ---
    sub do_dialog {
        # do whatever application script should do ...
    }

An application may have serveral doors.

All doors may do some initialization, which could be conveniently placed in a module:

    InitApp.pm
    package InitApp;
    sub init_application {
        my $cgi = shift; # may use this handle to the CGIsession object.
        ...
    }
    1;
    any door script: maindoor.cgi, sidedoor.cgi, backdoor.cgi ...
    #!/usr/bin/perl -w
    require InitApp;
    use CGIsession;
    $cgi = CGIsession->connect( -session_init => \&InitApp::init_application );
    do_dialog() unless $cgi->request_is_redirected();
    $cgi->save_session();
    # ---
    sub do_dialog {
        # do whatever your first application script should do ...
    }

If you pass the connect() method a code ref with -session_init=>, this code will be called if and when a new session storage is allocated by the request.

So it is best, when every script of an application has the -session_init argument in its CGIsession->connect() method call. If it is an legal first script of your application, it passes a reference to a sub with initialization code. Otherwise it passes a string with the name of a legal script, where the dialog should begin.

The initialization code may...

$cgi->setup_session_cookie( ... )
This is typically called from a -session_init code to setup the cookie for session tracking (if any).

Arguments:

$cgi->client_ip_changes( -continue_with=>$url )
Under normal conditions, client's IP address should not change from one request to an other. There are two possible exceptions:

a) Very dynamic IP-address management of some ISP's.

Mostly in Far East, ISP's do not have enough IP addresses for all customers. They work around this with local networks and a network address translator for communication with the outside world. Such address translators may assign each request an other IP address.

b) Hacker attack.

If a hacker spies a sessionID and tries to take over a session, he may not be clever enough to simulate the original IP address of the session. Spying of a sessionID should not be possible, if your users use SSL connections (https://), but would be rather simple without SSL (http://). Faking of clients IP address, would need rather advanced techniques and access to network routers or bridges.

With this method, the application can direct CGIsession to check the clients IP address with each request. If the client's IP address changes, that request can be redirected.

Arguments:

User Validation.

The door scripts may define login_dialog and recheck_password_dialog scripts, together with a lock_session_after idle time timeout and specify if the session_needs_validation.

Checking username and password is done in the dialog scripts written by you. CGIsession will redirect to them, whenever validation or revalidation is required.

The redirects to validation check scripts (login_dialog or recheck_password_dialog) will be done by $cgi->request_is_redirected() whenever validation is required. Your session_init code may define a default for session_needs_validation. Each script may override this default and so control, if it can be used by any user - or just by validated users.

It is a common problem with web application, that users just abandon a session, then forget about it and never do properly logout. An unauthorized person may then continue the session, when the user leaves his workstation without closing the browser.

The only way to complicate this for an unauthorized person is a timeout. If a user was inactive in a application for a given time, he will be asked for his password again, before he is allowed to continue his session.

CGIsession contains the code to check for a timeout. You must provide a script to recheck the password and enable timeout checking by calling appropriate methods of CGIsession. This can all be done in the session_init code by the 'door' scripts by calling the following methods:

$old_value = $cgi->login_dialog( $url )
Defines the login dialog script for this session. $url may be a relative or absolute URL, but should NOT be a full URL (with hostname). If $url is not passed, only the current value is returned.

The login_dialog() method is typically called from a -session_init=>code_reference passed to the connect() method by a door script to your application.

For an example see login.cgi below.

$old_value = $cgi->session_needs_validation( $boolean )
Defines whether this session needs validation. If $boolean is not passed or is undef, only the old value is returned. The session_needs_validation() method is typically called from a -session_init=>code_reference passed to the connect() method by a door script to your application to set a default.

Each script can override this default with a -needs_validation=>$boolean argument in its $cgi->request_is_redirected() call.

$cgi->request_is_redirected( )
$cgi->request_is_redirected( -needs_validation=>1 )
$cgi->request_is_redirected( -needs_validation=>0 )
$cgi->request_is_redirected( -needs_validation=>undef )
Decides if the current script should handle the request (in which case false is returned, i.e. the session is not redirected).

In three cases, the current script should not handle the request. In this cases $cgi->request_is_redirected() will write HTTP redirect headers and return true to inform the calling script, that it should not process the request and just terminate with calling $cgi->save_session(). The three cases are as follows:

a) A 'non-door' script is executed as the first script of a session. In this case, the request will be redirected to the 'door.cgi' script specified in CGIsession->connect( -session_init => 'door.cgi' ).

b) The client IP address changed. In this case, the request will be redirected to the URL specified in the $cgi->client_ip_changes( -continue_with => $url ) method call, which is typically in the session init code of 'door' scripts.

c) The validation of the user should be checked, before the current request can be processed. In this case, the current request is saved, then the request is redirected by writing HTTP redirect headers to the appropriate validation script and $cgi->request_is_redirected( ) returns true to signal its caller, that it should not (yet) process the request but terminate with a call to $cgi->save_session(). The validation script - which uses CGIsession as well - gains control, does its dialog and finally reports to CGIsession, that the $cgi->session_is_validated(). This method will itself write HTTP headers to redirect to the interrupted script, where CGIsession->connect() will restore the interrupted request and $cgi->request_is_redirected() will now return false to signal, that the request should be processed.

Arguments: there is just one argument, -needs_validation=>value, but the interpretation of value is as follows:

Any true value: the script needs a (not expired) validation. This overrides the session default.

0 or an empty string: the script needs no validation. This overrides the session default.

undef: the session default determinates.

The session default is set with the $cgi->session_needs_validation() method which is typically called from -session_init=>code_reference passed to the connect() method by a door script of your application. The defaults default is 0 (no validation required).

$old_value = $cgi->lock_session_after( $seconds )
Defines the maximal allowed idle time, after which CGIsession should recheck the validation. If $second is not passed or is undef, only the old value is returned. The lock_session_after() method is typically called from the login_dialog and/or recheck_password_dialog scripts. $seconds is typically a user selectable option of validation dialog forms. It should be a postive number. 0 (any false value, the default) disables session locking.

$cgi->force_timeout( )
Forces a timeout, i.e. locks the session. Only works when session locking is enabled.

$cgi->recheck_password_dialog( $url )
Analog to login_dialog, but the script supplied by $url should only check the password of an already validated user. $url may be a realtiv or absolute URL, but should not be a full URL (not start with https:// or http://). The method $cgi->recheck_password_dialog() is typically called in a -session_init=>code_reference passed to the connect() method by a door script of your application or from a login_dialog script upon successful validation.

$cgi->session_is_validated( )
$cgi->session_is_validated( -continue_with=>$url )
This method is called from login_dialog and recheck_password_dialog scripts, when the validation is checked. It will redirect to the scripts that was interrupted for a validation dialog.

Arguments:

$cgi->session_validation( )
Returns undef, if a session is not validated. Otherwise the Username specified by $cgi->session_is_validated( -validated_as=>username ), which defaults to 1 if never specified as a session_is_validated() argument.

Cleanup Abandoned Sessions.

$ndeleted = CGIsession->delete_sessions( ... )
$ndeleted = $cgi->delete_sessions( ... )
Deletes session storages. It is provided as a class method, which can also be called as an object method. So cronjobs my use it without creating a session. Or supervisor scripts may logout all other session. The method delete_sessions returns the number of deleted sessions. What will be deleted (=logouted) is defined with arguments. Each session is a candidate for deleting, if its last activity is more than -max_idle_time=>$seconds (or -force_max_idle_time=>$seconds) ago. This selection can be more restricted with additional arguments as follows:

Examples:

 # delete all sessions validated_as 'meier':
      $cgi->delete_sessions( -force_max_idle_time => 0,
                             -keep_unvalidated    => 1,
                             -delete_validated    => 'meier' );
 # delete all unvalidated sessions inactive since more than 15 min,
      # who did not set a prefered idle time max by calling allowed_idle_time()
      $cgi->delete_sessions( -max_idle_time  => 15*60,
                             -keep_validated => 1 );
 # delete all sessions from subnet 129.132.35.*
      $cgi->delete_sessions( -force_max_idle_time  => 0,
                             -ipv4_address => '^129\.132\.35\.' );
$cgi->allowed_idle_time( )
$cgi->allowed_idle_time( $seconds )
Get/set the maximal allowed idle time for the current session. Default is undefined. If a session sets its allowed idle time, that value overrides CGIsession->delete_sessions(-max_idle_time=>$seconds ) for this session.

I.e. when my session has not set an allowed idle time, CGIsession->delete_sessions(-max_idle_time=>1800) (typically running in a cronjob) would delete my session storage if its last modification date is older than 1800 seconds.

If my session has set $cgi->allowed_idle_time(3600), CGIsession->delete_sessions() would delete my session storage if its last modification date is older than 3600 seconds, regardless of any -max_idle_time argument of delete_sessions() (which is typically called from a cronjob).

Other Useful Methods.

$cgi->save_cgi_state( )
$cgi->save_cgi_state( $ident )
This method saves the CGI state (but not the session data) of the current request to a file in session storage. You may specify an $ident which is typically a number if you want to save different states for the same script.

$cgi->restore_cgi_state( )
$cgi->restore_cgi_state( $ident )
Restores a previous saved CGI state. The CGI state of the current request is replaced by the state saved by a previous save_cgi_state() of the same script and with the same $ident argument.

save_cgi_state() and restore_cgi_state() are provided as an alternative to CGI's save( FILEHANDLE ) and new( FILEHANDLE ) methods. They may be useful to implement navigation elements, which should restore the CGI state when the surfers navigates back to a previous visited place of an application.

$string = $cgi->random_name( )
$string = $cgi->random_name( $length )
Returns a random string consisting of [0-9A-Za-z] of $length characters. $length defaults to 12. It is used internally to generate sessionIDs and random names for protected_submits.

$lock = $cgi->set_lock( $path )
$lock = $cgi->set_lock( $path, $timeout )
This method is internally used to lock access to session data (in case of double clickers or framesets, several request may run in parallel). It is made public in case you want to use it as well. It implements a semaphore on a resource represented by a path to a filename. If the lock is older than $timeout seconds (defaulting to 10 seconds), the lock can be forced by the next process. To say it clearly: set_lock waits until the lock if free or it is older than $timeout seconds. This is useful for CGI scripts, because they can be aborted by surfers with the browsers STOP button. In this case, a script may not be able to return the lock. set_lock returns the absolute path to a file (in /tmp with extension .CGIlock) which is used for the semaphore.

$lock = $cgi->unlock( $lock );
Returns a lock set by set_lock. The argument should be the return value of the corresponding set_lock call.

$old_level = $cgi->session_debug( )
Debug control. Always returns the debug level. Arguments control debug settings:

Example: use this in a page footer subroutine to include collected debug messages at the end of the HTML page.

    $cgi->session_debug( -print=>1,
                         -string_before=>'<hr><h3>debug</h3><pre>',
                         -string_after=>'</pre><hr>' . $cgi->end_html() );

Example of an Application

main_door.cgi
This script will be used from casual surfers of your application. They will get some welcome first.
  #!/usr/bin/perl -w
  use strict;
  use vars qw($cgi);
  use CGIsession;
  $cgi = CGIsession->connect( -session_init => \&session_init );
  &print_welcome_page() unless $cgi->request_is_redirected;
  $cgi->save_session;
  sub session_init {
      my $cgi = shift;
      $cgi->login_dialog('login.cgi');   # define the login dialog
      $cgi->session_needs_validation(0); # does not yet need validation
  }
  sub print_welcome_page {
      print $cgi->header, $cgi->start_html, $cgi->h1("Hello Darling");
      print $cgi->link_to_script( 'anyscript.cgi', "Do something there"),
            $cgi->br;
      print $cgi->link_to_script( 'side_door.cgi', "Side door"),
            $cgi->br;
      if ($cgi->session_validation) {
           print $cgi->link_to_script( 'logout.cgi', "Logout");
      } else {
           print $cgi->link_to_script( 'login.cgi', "Login");
      }
      print $cgi->end_html;
 }

The connect() method specifies the local sub &session_init as -session_init code. In there is a login_dialog and session_needs_validation definition. Other typical CGIsession method calls in a session_init sub are $cgi->setup_session_cookie(...), $cgi->client_ip_changes(...) to install a handler for client ip address changes and $cgi->allowed_idle_time() to define a prefered inactivity time for method CGIsession->delete_sessions(...) which cleans up abandoned sessions. In a real application, the session_init code would be in a module, which could be used by any door script of the application.

The print_welcome_page() sub prints a HTML page. It contains links to other scripts, one of them may be a link to the login_dialog. This is not required. An application may run along until to the point, where user validation is required. That script would contain &handle_request() unless $cgi->request_is_redirected( needs_validation=>1 ) as shown below.

The Login link should be replaced by a Logout link after successful user validation. A script may use method $cgi->session_validation to find out if the session is validated.

side_door.cgi
This is the entry point for your power user. They should validate right away, hence it calls $cgi->request_is_redirected( needs_validation=>1 ). A not validated session will be redirected to the login_dialog by $cgi->request_is_redirected; The CGI state of side_door.cgi is saved before redirection and will be restored, when the login_dialog terminates and redirects back to the side_door.cgi.
  #!/usr/bin/perl -w
  use strict;
  use vars qw($cgi);
  use CGIsession;
  $cgi = CGIsession->connect( -session_init => \&session_init );
  &print_action_menue_page()
      unless $cgi->request_is_redirected( needs_validation=>1 );
  $cgi->save_session;
  sub session_init {
      my $cgi = shift;
      $cgi->login_dialog('login.cgi');   # define the login dialog
      $cgi->session_needs_validation(0); # application default
  }
  sub print_action_menue_page {
      my $name = $cgi->session_validation;
      print $cgi->header, $cgi->start_html, $cgi->h1("Hi $name");
      # ...
      print $cgi->link_to_script( 'anyscript.cgi', "Do something there");
      # ...
      print $cgi->end_html;
  }

anyscript.cgi
This is a script that will be used within the application. It is not a good place to start for a surfer; hence it declares -session_init=>'main_door.cgi' at its connect() method call. Naturally, a real application would have many scripts of this kind.
  #!/usr/bin/perl -w
  use strict;
  use vars qw($cgi);
  use CGIsession;
  $cgi = CGIsession->connect( -session_init => 'main_door.cgi' );
  &handle_request() unless $cgi->request_is_redirected;
  $cgi->save_session;
  sub handle_request {
      print $cgi->header, $cgi->start_html, $cgi->h1("Something should be here");
      print $cgi->p("But there is nothing yet...");
      unless ($cgi->session_validation) {
          print $cgi->link_to_script( 'login.cgi', "Login"), $cgi->br;
      }
      print $cgi->link_to_script( 'main_door.cgi', "Main door"), $cgi->br;
      print $cgi->link_to_script( 'side_door.cgi', "Side door"), $cgi->br;
      print $cgi->link_to_script( 'logout.cgi', "Logout"), $cgi->br;
      print $cgi->end_html;
  }

If this script would contain any dialog reserved for validated users, it would need just one line changed:

  &handle_request() unless $cgi->request_is_redirected( needs_validation=>1 );

On the other hand, if the application default would require session validation, but the script will just display some help info, it may allow unvalidated users with

  &handle_request() unless $cgi->request_is_redirected( needs_validation=>0 );

A script could inspect its $cgi->param()s to decide, if it needs validation or not. But it should not handle the request before calling request_is_redirected.

login.cgi
Anyscript.cgi type scripts may redirect to login.cgi if they use the above mentioned unless $cgi->request_is_redirected( needs_validation=>1 ) On the other hand, a surfer may follow a link to this script as shown in main_door.cgi.

login.cgi needs_validation=>0 in its $cgi->request_is_redirected call, because it handles user validation itself, it does naturally not need a session with a valid user validation as a prerequisit to do its dialog. CGIsession would not redirect to the same script when calling a handler, but to specify needs_validation=>0 is recommended anyway.

In sub do_login_dialog there is first a check, if a valid login form was submitted. In case of a 'login', the username and password should be checked against a database. If ok, method lock_session_after() is used to define a maximum idle time. This lets CGIsession check in the later dialog, if there was a pause greater than $seconds, in that case CGIsession will recheck the users password. This handler is defined with recheck_password_dialog(). The default for session_needs_validation is set to true. Then abondoned sessions of the same user (if any exist) would be deleted with delete_sessions() and finally the session is reported as validated by calling session_is_validated.

session_is_validated() will write HTTP headers to redirect the request. If an other request was interrupted for user validation, that request will be continued. Otherwise - if the user clicked a 'Login' link somewhere (as shown in main_door.cgi), there is no interrupted script and session_is_validated() will redirect to the URL given with -continue_with=>'main_door.cgi'.

In case of a 'Cancel', the session is reported as not validated and redirection will go to -force_continue_with=>'main_door.cgi', independent of any interrupted request.

The Login page is strait forward. It uses a $cgi->protected_submit() as 'Login' submit button. This is the first submit button, so some browsers will simulate a click on this submit button when the surfer hits the ENTER key; other browsers would send the request without any submit buttons, in that case CGIsession will insert the missing submit input. So submit buttons will behave the same way independent on which browser the surfer uses.

  #!/usr/bin/perl -w
  use strict;
  use vars qw($cgi);
  use CGIsession;
  $cgi = CGIsession->connect();
  &do_login_dialog() unless $cgi->request_is_redirected( needs_validation=>0 );
  $cgi->save_session;
  sub do_login_dialog {
      my $er_msg = '';
      if ($cgi->param('login')) {
          my $username = $cgi->param('username');
          # ... check username and password; set $er_msg on errors
          unless ($er_msg) {
              $cgi->lock_session_after($cgi->param('timeout'));
              $cgi->recheck_password_dialog('recheck_password.cgi');
              $cgi->session_needs_validation(1); # we are validated, may recheck pw
              # logout abandoned sessions of that user:
              $cgi->delete_sessions( -force_max_idle_time=>0,
                                     -keep_own_session=>1,
                                     -application=>$cgi,
                                     -keep_unvalidated=>1,
                                     -delete_validated=>$username );
              $cgi->session_is_validated ( -validated_as=>$username,
                                           -continue_with=>'main_door.cgi' );
              return;
          }
      } elsif ($cgi->param('cancel')) {
          $cgi->session_needs_validation(0); # no validation yet
          $cgi->session_is_validated ( -validated_as=>undef,
                                       -force_continue_with=>'main_door.cgi' );
          return;
      }
      print $cgi->header, $cgi->start_html, $cgi->startform;
      print $cgi->h2($er_msg) if ($er_msg);
      print "Username: ",
            $cgi->textfield(-name=>'username', -size=>20),
            $cgi->br;
      print "Password: ",
            $cgi->password_field(-name=>'password', -size=>20),
            $cgi->br;
      print "Timout after: ",
            $cgi->popup_menu(-name=>'timeout', -values=>[30, 300, 3000]),
            " seconds",
            $cgi->br;
      print $cgi->protected_submit(-name=>'login', -value=>'Login');
      print $cgi->submit(-name=>'cancel', -value=>'Cancel');
      print $cgi->endform, $cgi->end_html;
  }

recheck_password.cgi
This recheck_password_dialog is installed in login.cgi after a successful validation.

It will be called after a timeout (too much inactivity time) in a validated session. It should never be called through a direct link to it, and it should never be used as an entry to your application; hence $cgi = CGIsession->connect( -session_init=>'main_door.cgi' ).

The dialog asks just for the password. The username is retrieved from $cgi->session_validation, and is the same as defined by login.cgi in its $cgi->session_is_validated ( -validated_as=>$username ...) call.

When the password check is ok, recheck_password.cgi calls $cgi->session_is_validated and writes no response of its own. Method $cgi->session_is_validated writes HTTP redirect headers to return to the interrupted script that caused revalidation. That scripts CGI state was saved and will now be restored, so the surfer just continues his dialog after reentering his password.

  #!/usr/bin/perl -w
  use strict;
  use vars qw($cgi);
  use CGIsession;
  $cgi = CGIsession->connect( -session_init=>'main_door.cgi' );
  &do_login_dialog()
      unless $cgi->request_is_redirected( needs_validation=>0 );
  $cgi->save_session;
  sub do_login_dialog {
      my $er_msg = '';
      if ($cgi->param('login')) {
          my $username = $cgi->session_validation;
          # ... check username and pasword; set $er_msg
          unless ($er_msg) {
              $cgi->lock_session_after($cgi->param('timeout'));
              $cgi->session_is_validated;
              return;
          }
      } elsif ($cgi->param('cancel')) {
          $cgi->session_needs_validation(0); # no validation any more
          $cgi->session_is_validated ( -validated_as=>undef,
                                       -force_continue_with=>'logout.cgi' );
          return;
      }
      print $cgi->header, $cgi->start_html, $cgi->startform;
      print $cgi->h2($er_msg) if ($er_msg);
      print "Password: ",
            $cgi->password_field(-name=>'password', -size=>20),
            $cgi->br;
      print "Timout after: ",
            $cgi->popup_menu(-name=>'timeout',
                             -values=>[30, 300, 3000],
                             -default=>$cgi->lock_session_after() ),
            " seconds",
            $cgi->br;
      print $cgi->protected_submit(-name=>'login', -value=>'Login');
      print $cgi->submit(-name=>'cancel', -value=>'Cancel');
      print $cgi->endform, $cgi->end_html;
  }

logout.cgi
Logout -needs_validation=>0. But we check $cgi->request_is_redirected() anyway, because a surfer may have bookmarked this page, in which case he should be redirected to -session_init=>'main_door.cgi' as specified in connect().

The $cgi->delete_session() call is before a $cgi->header() or $cgi->redirect() call, to allow CGIsession to add a dummy cookie that should delete the session cookie in the browser.

Any Links are written with the CGI $cgi->a() method, not with $cgi->link_to_script(), because we don't want any more session tracking after the session is deleted.

  #!/usr/bin/perl -w
  use strict;
  use vars qw($cgi);
  use CGIsession;
  $cgi = CGIsession->connect( -session_init=>'main_door.cgi' );
  &print_bye_bye_page() unless $cgi->request_is_redirected ( -needs_validation=>0 );
 w sub print_bye_bye_page {
      $cgi->delete_session;
      print $cgi->header, $cgi->start_html, $cgi->h1("Bye Bye Darling");
      print $cgi->a( { href=>'main_door.cgi' }, "Restart Application"), $cgi->br;
      print $cgi->a( { href=>'side_door.cgi' }, "Restart Application by side door"),
            $cgi->br;
      print $cgi->a( { href=>'anyscript.cgi' }, "Restart Application by anyscript"),
            $cgi->br;
      print $cgi->end_html;
 }

Hints For CGIsession Programmers


ALPHABETICAL LIST OF METHODS

allowed_idle_time( ) Sets the prefered maximum idle time for a session, before it should be deleted by delete_sessions(-max_idle_time=>$sec).

 $old_value = $cgi->allowed_idle_time();           # get only
 $old_value = $cgi->allowed_idle_time( $seconds ); # get and set

application_directory( ) Returns the absolut path to a directory (without terminating /) where application private files may be allocated (shared by all sessions of that application).

 $absolute_path = $cgi->application_directory(); # no arguments

client_ip_changes( ) Defines what CGIsession should do, if the clients ip address changes.

 $cgi->client_ip_changes( -continue_with => $url,
                          -ipv4_mask     => $nbits,
                          -ipv6_mask     => not yet implemented );

connect( ) Object constructor.

 $cgi = CGIsession->connect();
 $cgi = CGIsession->connect( -session_init    => $relativ_url, ...
 $cgi = CGIsession->connect( -session_init    => \&coderef, ...
                             -application     => 'apname',
                             -debug           => $level,
                             -debug_mode      => 'warn',
                             -no_submit_check => 1,
                           );

delete_private_data( ) Deletes a data from the private data storage.

 $old_value = $cgi->delete_private_data( $key );

delete_session_data( ) Deletes a data from the session data storage which is shared by all scripts of a session.

 $old_value = $cgi->delete_session_data( $key );

delete_session( ) Deletes the current session.

 $cgi->delete_session(); # no arguments

delete_sessions( ) Deletes sessions. May be called as an object method or as a class method.

 CGIsession->delete_sessions ( ...
 $cgi->delete_sessions( -max_idle_time       => $seconds, ...
 $cgi->delete_sessions( -force_max_idle_time => $seconds, ...
                        -debug               => $level,
                        -application         => $apname,
                                             => $cgi,
                        -keep_unvalidated    => 1,
                        -keep_own_session    => 1,
                        -session             => $sessioID,
                        -keep_validated      => 1,
                                             => $userame,
                                             =>{user1=>1, user2=>1 ... },
                                             =>['user1', 'user2'],
                        -delete_validated    =>1,
                                             =>$userame,
                                             =>{user1=>1, user2=>1 ... },
                                             =>['user1', 'user2'],
                      );

end_form( ) Alias for $cgi->endform();

 $string = $cgi->end_form(); # arguments see CGI endform(): there are none

endform( ) </FORM>-HTML tag which may have a hidden field added.

 $string = $cgi->endform(); # arguments see CGI: there are none

force_timeout( ) locks the session; the surfer must reenter his password to continue

 force_timeout(); # no arguments

header( ) HTTP headers, which may have an additional cookie set.

 $string = $cgi->header(); # arguments see CGI

link_to_script( ) Returns a <a href=...> ... </a> tag.

 $string = $cgi->link_to_script( '', 'visible text' );
 $string = $cgi->link_to_script( $url, 'visible text' )
 $string = $cgi->link_to_script( {href=>$url, ...}, 'visible text' );
 $string = $cgi->link_to_script( '',
                                 'visible text',
                                '?key1=v1;key2=v2' );
 $string = $cgi->link_to_script( $url,
                                 'visible text',
                                 '?key1=v1;key2=v2' );
 $string = $cgi->link_to_script( {href=>$url, ...},
                                 'visible text',
                                 '?key1=v1;key2=v2' );
 $string = $cgi->link_to_script( '',
                                 'visible text',
                                 [key1=>'v1', key2=>'v2' ...] );
 $string = $cgi->link_to_script( $url,
                                 'visible text',
                                 [key1=>'v1', key2=>'v2' ...] );
 $string = $cgi->link_to_script( {href=>$url, ...},
                                 'visible text',
                                 [key1=>'v1', key2=>'v2' ...] );

lock_session_after( ) Defines a maximal idle time for validated sessions. If the session is idles longer than that, it will be revalidated.

 $old_value = $cgi->lock_session_after();             # get only
 $old_value = $cgi->lock_session_after( $new_value ); # get and set

login_dialog( ) Installs a login_dialog handler.

 $old_value = $cgi->login_dialog();        # get only
 $old_value = $cgi->login_dialog( $url );  # get and set

new( ) Hides CGI's new method. Alias for connect(...).

 $cgi = CGIsession->new() # see connect method.

private_data( ) Get / set data private to a script and a session.

 $old_value = $cgi->private_data( $key );                # get only
 $old_value = $cgi->private_data( $key => $new_value );  # get and set
 $href = $cgi->private_data( );  # get a reference to the CGI internal hash

private_data_keys( ) Keys in private_data

 @array = $cgi->private_data_keys();  # no arguments

protected_submit( ) Returns a submit button HTML tag.

 $string = $cgi->protected_submit( -name=>..., -value=>...,
                                   -repeated_name=>...,
                                   -repeated_value=>...,
                                 ); # more arguments see CGI submit.

random_name( ) Returns a random name.

 $string = $cgi->random_name();          # default length 12
 $string = $cgi->random_name( $length );

recheck_password_dialog( ) Installs a recheck_validation_handler

 $old_value = $cgi->recheck_password_dialog();        # get only
 $old_value = $cgi->recheck_password_dialog( $url );  # get and set

redirect( ) Returns redirect HTTP headers which may include an additional cookie.

 $string = $cgi->redirect( ) # arguments see CGI, but URL should be relative
                             # or absolute when redirecting to a script of the
                             # same application (i.e. if the sessionID should
                             # be passed to the URL)

request_is_redirected( ) Checks if the current script should handle the current request.

 $boolean = $cgi->request_is_redirected(); # use session default needs_validation
 $boolean = $cgi->request_is_redirected( -needs_validation => 1 );
 $boolean = $cgi->request_is_redirected( -needs_validation => 0 );
 $boolean = $cgi->request_is_redirected( -needs_validation => undef ); # session default

restore_cgi_state( ) Replaces the CGI state of the current request with a previous saved CGI state (same script, same $ident).

 $cgi->restore_cgi_state();           # default ident '0'
 $cgi->restore_cgi_state( $ident );

save_cgi_state( ) Saves the CGI state of the current request.

 $cgi->save_cgi_state();              # default ident '0'
 $cgi->save_cgi_state( $ident );

save_session( ) Saves session internal data to files.

 $cgi->save_session();  # no arguments

session_data( ) Access to data, shared by all scripts of a session.

 $old_value = $cgi->session_data( $key );               # get only
 $old_value = $cgi->session_data( $key => $new_value ); # get and set
 $href = $cgi->session_data();  # get a ref to the CGIsession internal hash

session_data_keys( ) Keys in session_data

 @array = $cgi->session_data_keys();  # no arguments

session_debug( ) Controls session debug.

 $old_level = $cgi->session_debug()
 $old_level = $cgi->session_debug( ...
                                   -level         => $level,
                                   -mode          => 'warn',
                                   -print         => 1,
                                   -string_before => $string,
                                   -string_after  => $string,
                                  );

session_directory( ) Returns the absolute path to a directory (without terminating /); private for the current session.

 $absolute_path = $cgi->session_directory(); # no arguments

sessionID( ) returns the internal sessionID;

 $string = $cgi->sessionID; # for REST applications only

session_is_validated( ) Writes HTTP headers to redirect after a validation dialog.

 $cgi->session_is_validated();  # typical use in recheck_password handler
 $cgi->session_is_validated( -continue_with => $url,
                             -validated_as  => $username
                            ); # typical use in a CGI login script
 $cgi->session_is_validated( -force_continue_with => $url,
                             -validated_as        => 0
                            ); # typical use in a CGI script after a cancel
 $cgi->session_is_validated( -no_redirect  => 1,
                             -validated_as => $username); # Typical use in REST

session_needs_validation( ) Defines session default.

 $old_value = $cgi->session_needs_validation();             # get only
 $old_value = $cgi->session_needs_validation( $new_value ); # get and set

session_validation( ) Returns session validation info defined in a login_dialog handler by calling session_is_validated(-validated_as=>$username).

 $username = $cgi->session_validation(); # no arguments

setup_session_cookie( ) Sets arguments for the cookie to be used for session tracking. Called only from within a session_init code ref.

 $cgi->setup_session_cookie( -no_cookie=>1 ); # use URL-rewrite
 $cgi->setup_session_cookie( -cookie_path    => '/',
                             -cookie_domain  => $own_host,
                             -cookie_expires => '+12h',
                             -cookie_secure  => 1,
                           );

set_lock( ) Semaphor lock with timeout

 $lock_ident = $cgi->set_lock( $path );           # timeout default 10 seconds
 $lock_ident = $cgi->set_lock( $path, $timeout );

start_form( ) Alias for $cgi->startform()

 $string = $cgi->start_form( );   # arguments see CGI startform()

start_multipart_form( ) Returns a HTML <form> tag.

 $string = $cgi->start_multipart_form( );  # arguments see CGI

startform( ) Returns a HTML <form> tag.

 $string = $cgi->startform( );  # arguments see CGI

submit( ) Returns a HTML submit button tag.

 $string = $cgi->submit( );  # arguments see CGI

unlock( ) Semaphor lock with timeout

 $cgi->unlock( $lock_ident )

dmsg( ) Internally used for debug messages.

 $cgi->dmsg( $string ) if $cgi->{debug} > $level;


FILES

CGIsession will generate /tmp/CGIsessions/ a directory in which session storages will be kept. The lock mechanism will generate files in /tmp with extension .CGIlock.


SEE ALSO

See CGI.pm from L. Stein in CPAN.


COPYRIGHT

Copyright 2004-2013 Thedi gerber@id.ethz.ch

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.