Add built local::lib
[catagits/Gitalist.git] / local-lib5 / man / man3 / Catalyst::Plugin::Session.3pm
diff --git a/local-lib5/man/man3/Catalyst::Plugin::Session.3pm b/local-lib5/man/man3/Catalyst::Plugin::Session.3pm
new file mode 100644 (file)
index 0000000..5acb958
--- /dev/null
@@ -0,0 +1,616 @@
+.\" Automatically generated by Pod::Man 2.22 (Pod::Simple 3.10)
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings.  \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote.  \*(C+ will
+.\" give a nicer C++.  Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available.  \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+.    ds -- \(*W-
+.    ds PI pi
+.    if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+.    if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\"  diablo 12 pitch
+.    ds L" ""
+.    ds R" ""
+.    ds C` ""
+.    ds C' ""
+'br\}
+.el\{\
+.    ds -- \|\(em\|
+.    ds PI \(*p
+.    ds L" ``
+.    ds R" ''
+'br\}
+.\"
+.\" Escape single quotes in literal strings from groff's Unicode transform.
+.ie \n(.g .ds Aq \(aq
+.el       .ds Aq '
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
+.\" entries marked with X<> in POD.  Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.ie \nF \{\
+.    de IX
+.    tm Index:\\$1\t\\n%\t"\\$2"
+..
+.    nr % 0
+.    rr F
+.\}
+.el \{\
+.    de IX
+..
+.\}
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear.  Run.  Save yourself.  No user-serviceable parts.
+.    \" fudge factors for nroff and troff
+.if n \{\
+.    ds #H 0
+.    ds #V .8m
+.    ds #F .3m
+.    ds #[ \f1
+.    ds #] \fP
+.\}
+.if t \{\
+.    ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+.    ds #V .6m
+.    ds #F 0
+.    ds #[ \&
+.    ds #] \&
+.\}
+.    \" simple accents for nroff and troff
+.if n \{\
+.    ds ' \&
+.    ds ` \&
+.    ds ^ \&
+.    ds , \&
+.    ds ~ ~
+.    ds /
+.\}
+.if t \{\
+.    ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+.    ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+.    ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+.    ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+.    ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+.    ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+.    \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+.    \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+.    \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+.    ds : e
+.    ds 8 ss
+.    ds o a
+.    ds d- d\h'-1'\(ga
+.    ds D- D\h'-1'\(hy
+.    ds th \o'bp'
+.    ds Th \o'LP'
+.    ds ae ae
+.    ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "Catalyst::Plugin::Session 3"
+.TH Catalyst::Plugin::Session 3 "2009-11-04" "perl v5.8.7" "User Contributed Perl Documentation"
+.\" For nroff, turn off justification.  Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.if n .ad l
+.nh
+.SH "NAME"
+Catalyst::Plugin::Session \- Generic Session plugin \- ties together server side storage and client side state required to maintain session data.
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+.Vb 1
+\&    # To get sessions to "just work", all you need to do is use these plugins:
+\&
+\&    use Catalyst qw/
+\&      Session
+\&      Session::Store::FastMmap
+\&      Session::State::Cookie
+\&      /;
+\&
+\&    # you can replace Store::FastMmap with Store::File \- both have sensible
+\&    # default configurations (see their docs for details)
+\&
+\&    # more complicated backends are available for other scenarios (DBI storage,
+\&    # etc)
+\&
+\&
+\&    # after you\*(Aqve loaded the plugins you can save session data
+\&    # For example, if you are writing a shopping cart, it could be implemented
+\&    # like this:
+\&
+\&    sub add_item : Local {
+\&        my ( $self, $c ) = @_;
+\&
+\&        my $item_id = $c\->req\->param("item");
+\&
+\&        # $c\->session is a hash ref, a bit like $c\->stash
+\&        # the difference is that it\*(Aq preserved across requests
+\&
+\&        push @{ $c\->session\->{items} }, $item_id;
+\&
+\&        $c\->forward("MyView");
+\&    }
+\&
+\&    sub display_items : Local {
+\&        my ( $self, $c ) = @_;
+\&
+\&        # values in $c\->session are restored
+\&        $c\->stash\->{items_to_display} =
+\&          [ map { MyModel\->retrieve($_) } @{ $c\->session\->{items} } ];
+\&
+\&        $c\->forward("MyView");
+\&    }
+.Ve
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The Session plugin is the base of two related parts of functionality required
+for session management in web applications.
+.PP
+The first part, the State, is getting the browser to repeat back a session key,
+so that the web application can identify the client and logically string
+several requests together into a session.
+.PP
+The second part, the Store, deals with the actual storage of information about
+the client. This data is stored so that the it may be revived for every request
+made by the same client.
+.PP
+This plugin links the two pieces together.
+.SH "RECOMENDED BACKENDS"
+.IX Header "RECOMENDED BACKENDS"
+.IP "Session::State::Cookie" 4
+.IX Item "Session::State::Cookie"
+The only really sane way to do state is using cookies.
+.IP "Session::Store::File" 4
+.IX Item "Session::Store::File"
+A portable backend, based on Cache::File.
+.IP "Session::Store::FastMmap" 4
+.IX Item "Session::Store::FastMmap"
+A fast and flexible backend, based on Cache::FastMmap.
+.SH "METHODS"
+.IX Header "METHODS"
+.IP "sessionid" 4
+.IX Item "sessionid"
+An accessor for the session \s-1ID\s0 value.
+.IP "session" 4
+.IX Item "session"
+Returns a hash reference that might contain unserialized values from previous
+requests in the same session, and whose modified value will be saved for future
+requests.
+.Sp
+This method will automatically create a new session and session \s-1ID\s0 if none
+exists.
+.Sp
+You can also set session keys by passing a list of key/value pairs or a
+hashref.
+.Sp
+.Vb 3
+\&    $c\->session\->{foo} = "bar";      # This works.
+\&    $c\->session(one => 1, two => 2); # And this.
+\&    $c\->session({ answer => 42 });   # And this.
+.Ve
+.IP "session_expires" 4
+.IX Item "session_expires"
+.PD 0
+.ie n .IP "session_expires $reset" 4
+.el .IP "session_expires \f(CW$reset\fR" 4
+.IX Item "session_expires $reset"
+.PD
+This method returns the time when the current session will expire, or 0 if
+there is no current session. If there is a session and it already expired, it
+will delete the session and return 0 as well.
+.Sp
+If the \f(CW$reset\fR parameter is true, and there is a session \s-1ID\s0 the expiry time
+will be reset to the current time plus the time to live (see
+\&\*(L"\s-1CONFIGURATION\s0\*(R"). This is used when creating a new session.
+.IP "flash" 4
+.IX Item "flash"
+This is like Ruby on Rails' flash data structure. Think of it as a stash that
+lasts for longer than one request, letting you redirect instead of forward.
+.Sp
+The flash data will be cleaned up only on requests on which actually use
+\&\f(CW$c\fR\->flash (thus allowing multiple redirections), and the policy is to delete
+all the keys which haven't changed since the flash data was loaded at the end
+of every request.
+.Sp
+.Vb 2
+\&    sub moose : Local {
+\&        my ( $self, $c ) = @_;
+\&
+\&        $c\->flash\->{beans} = 10;
+\&        $c\->response\->redirect( $c\->uri_for("foo") );
+\&    }
+\&
+\&    sub foo : Local {
+\&        my ( $self, $c ) = @_;
+\&
+\&        my $value = $c\->flash\->{beans};
+\&
+\&        # ...
+\&
+\&        $c\->response\->redirect( $c\->uri_for("bar") );
+\&    }
+\&
+\&    sub bar : Local {
+\&        my ( $self, $c ) = @_;
+\&
+\&        if ( exists $c\->flash\->{beans} ) { # false
+\&
+\&        }
+\&    }
+.Ve
+.IP "clear_flash" 4
+.IX Item "clear_flash"
+Zap all the keys in the flash regardless of their current state.
+.ie n .IP "keep_flash @keys" 4
+.el .IP "keep_flash \f(CW@keys\fR" 4
+.IX Item "keep_flash @keys"
+If you want to keep a flash key for the next request too, even if it hasn't
+changed, call \f(CW\*(C`keep_flash\*(C'\fR and pass in the keys as arguments.
+.IP "delete_session \s-1REASON\s0" 4
+.IX Item "delete_session REASON"
+This method is used to invalidate a session. It takes an optional parameter
+which will be saved in \f(CW\*(C`session_delete_reason\*(C'\fR if provided.
+.Sp
+\&\s-1NOTE:\s0 This method will \fBalso\fR delete your flash data.
+.IP "session_delete_reason" 4
+.IX Item "session_delete_reason"
+This accessor contains a string with the reason a session was deleted. Possible
+values include:
+.RS 4
+.IP "\(bu" 4
+\&\f(CW\*(C`address mismatch\*(C'\fR
+.IP "\(bu" 4
+\&\f(CW\*(C`session expired\*(C'\fR
+.RE
+.RS 4
+.RE
+.ie n .IP "session_expire_key $key, $ttl" 4
+.el .IP "session_expire_key \f(CW$key\fR, \f(CW$ttl\fR" 4
+.IX Item "session_expire_key $key, $ttl"
+Mark a key to expire at a certain time (only useful when shorter than the
+expiry time for the whole session).
+.Sp
+For example:
+.Sp
+.Vb 1
+\&    _\|_PACKAGE_\|_\->config(\*(AqPlugin::Session\*(Aq => { expires => 1000000000000 }); # forever
+\&
+\&    # later
+\&
+\&    $c\->session_expire_key( _\|_user => 3600 );
+.Ve
+.Sp
+Will make the session data survive, but the user will still be logged out after
+an hour.
+.Sp
+Note that these values are not auto extended.
+.IP "change_session_id" 4
+.IX Item "change_session_id"
+By calling this method you can force a session id change while keeping all
+session data. This method might come handy when you are paranoid about some
+advanced variations of session fixation attack.
+.Sp
+If you want to prevent this session fixation scenario:
+.Sp
+.Vb 5
+\&    0) let us have WebApp with anonymous and authenticated parts
+\&    1) a hacker goes to vulnerable WebApp and gets a real sessionid,
+\&       just by browsing anonymous part of WebApp
+\&    2) the hacker inserts (somehow) this values into a cookie in victim\*(Aqs browser
+\&    3) after the victim logs into WebApp the hacker can enter his/her session
+.Ve
+.Sp
+you should call change_session_id in your login controller like this:
+.Sp
+.Vb 8
+\&      if ($c\->authenticate( { username => $user, password => $pass } )) {
+\&        # login OK
+\&        $c\->change_session_id;
+\&        ...
+\&      } else {
+\&        # login FAILED
+\&        ...
+\&      }
+.Ve
+.SH "INTERNAL METHODS"
+.IX Header "INTERNAL METHODS"
+.IP "setup" 4
+.IX Item "setup"
+This method is extended to also make calls to
+\&\f(CW\*(C`check_session_plugin_requirements\*(C'\fR and \f(CW\*(C`setup_session\*(C'\fR.
+.IP "check_session_plugin_requirements" 4
+.IX Item "check_session_plugin_requirements"
+This method ensures that a State and a Store plugin are also in use by the
+application.
+.IP "setup_session" 4
+.IX Item "setup_session"
+This method populates \f(CW\*(C`$c\->config(\*(AqPlugin::Session\*(Aq)\*(C'\fR with the default values
+listed in \*(L"\s-1CONFIGURATION\s0\*(R".
+.IP "prepare_action" 4
+.IX Item "prepare_action"
+This method is extended.
+.Sp
+Its only effect is if the (off by default) \f(CW\*(C`flash_to_stash\*(C'\fR configuration
+parameter is on \- then it will copy the contents of the flash to the stash at
+prepare time.
+.IP "finalize_headers" 4
+.IX Item "finalize_headers"
+This method is extended and will extend the expiry time before sending
+the response.
+.IP "finalize_body" 4
+.IX Item "finalize_body"
+This method is extended and will call finalize_session before the other
+finalize_body methods run.  Here we persist the session data if a session exists.
+.IP "initialize_session_data" 4
+.IX Item "initialize_session_data"
+This method will initialize the internal structure of the session, and is
+called by the \f(CW\*(C`session\*(C'\fR method if appropriate.
+.IP "create_session_id" 4
+.IX Item "create_session_id"
+Creates a new session \s-1ID\s0 using \f(CW\*(C`generate_session_id\*(C'\fR if there is no session \s-1ID\s0
+yet.
+.IP "validate_session_id \s-1SID\s0" 4
+.IX Item "validate_session_id SID"
+Make sure a session \s-1ID\s0 is of the right format.
+.Sp
+This currently ensures that the session \s-1ID\s0 string is any amount of case
+insensitive hexadecimal characters.
+.IP "generate_session_id" 4
+.IX Item "generate_session_id"
+This method will return a string that can be used as a session \s-1ID\s0. It is
+supposed to be a reasonably random string with enough bits to prevent
+collision. It basically takes \f(CW\*(C`session_hash_seed\*(C'\fR and hashes it using \s-1SHA\-1\s0,
+\&\s-1MD5\s0 or \s-1SHA\-256\s0, depending on the availability of these modules.
+.IP "session_hash_seed" 4
+.IX Item "session_hash_seed"
+This method is actually rather internal to generate_session_id, but should be
+overridable in case you want to provide more random data.
+.Sp
+Currently it returns a concatenated string which contains:
+.RS 4
+.IP "\(bu" 4
+A counter
+.IP "\(bu" 4
+The current time
+.IP "\(bu" 4
+One value from \f(CW\*(C`rand\*(C'\fR.
+.IP "\(bu" 4
+The stringified value of a newly allocated hash reference
+.IP "\(bu" 4
+The stringified value of the Catalyst context object
+.RE
+.RS 4
+.Sp
+in the hopes that those combined values are entropic enough for most uses. If
+this is not the case you can replace \f(CW\*(C`session_hash_seed\*(C'\fR with e.g.
+.Sp
+.Vb 6
+\&    sub session_hash_seed {
+\&        open my $fh, "<", "/dev/random";
+\&        read $fh, my $bytes, 20;
+\&        close $fh;
+\&        return $bytes;
+\&    }
+.Ve
+.Sp
+Or even more directly, replace \f(CW\*(C`generate_session_id\*(C'\fR:
+.Sp
+.Vb 6
+\&    sub generate_session_id {
+\&        open my $fh, "<", "/dev/random";
+\&        read $fh, my $bytes, 20;
+\&        close $fh;
+\&        return unpack("H*", $bytes);
+\&    }
+.Ve
+.Sp
+Also have a look at Crypt::Random and the various openssl bindings \- these
+modules provide APIs for cryptographically secure random data.
+.RE
+.IP "finalize_session" 4
+.IX Item "finalize_session"
+Clean up the session during \f(CW\*(C`finalize\*(C'\fR.
+.Sp
+This clears the various accessors after saving to the store.
+.IP "dump_these" 4
+.IX Item "dump_these"
+See \*(L"dump_these\*(R" in Catalyst \- ammends the session data structure to the list of
+dumped objects if session \s-1ID\s0 is defined.
+.IP "calculate_extended_session_expires" 4
+.IX Item "calculate_extended_session_expires"
+.PD 0
+.IP "calculate_initial_session_expires" 4
+.IX Item "calculate_initial_session_expires"
+.IP "create_session_id_if_needed" 4
+.IX Item "create_session_id_if_needed"
+.IP "delete_session_id" 4
+.IX Item "delete_session_id"
+.IP "extend_session_expires" 4
+.IX Item "extend_session_expires"
+.IP "extend_session_id" 4
+.IX Item "extend_session_id"
+.IP "get_session_id" 4
+.IX Item "get_session_id"
+.IP "reset_session_expires" 4
+.IX Item "reset_session_expires"
+.IP "session_is_valid" 4
+.IX Item "session_is_valid"
+.IP "set_session_id" 4
+.IX Item "set_session_id"
+.PD
+.SH "USING SESSIONS DURING PREPARE"
+.IX Header "USING SESSIONS DURING PREPARE"
+The earliest point in time at which you may use the session data is after
+Catalyst::Plugin::Session's \f(CW\*(C`prepare_action\*(C'\fR has finished.
+.PP
+State plugins must set \f(CW$c\fR\->session \s-1ID\s0 before \f(CW\*(C`prepare_action\*(C'\fR, and during
+\&\f(CW\*(C`prepare_action\*(C'\fR Catalyst::Plugin::Session will actually load the data from
+the store.
+.PP
+.Vb 2
+\&    sub prepare_action {
+\&        my $c = shift;
+\&
+\&        # don\*(Aqt touch $c\->session yet!
+\&
+\&        $c\->NEXT::prepare_action( @_ );
+\&
+\&        $c\->session;  # this is OK
+\&        $c\->sessionid; # this is also OK
+\&    }
+.Ve
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+.Vb 3
+\&    $c\->config(\*(AqPlugin::Session\*(Aq => {
+\&        expires => 1234,
+\&    });
+.Ve
+.PP
+All configuation parameters are provided in a hash reference under the
+\&\f(CW\*(C`Plugin::Session\*(C'\fR key in the configuration hash.
+.IP "expires" 4
+.IX Item "expires"
+The time-to-live of each session, expressed in seconds. Defaults to 7200 (two
+hours).
+.IP "verify_address" 4
+.IX Item "verify_address"
+When true, \f(CW\*(C`<$c\-\*(C'\fRrequest\->address>> will be checked at prepare time. If it is
+not the same as the address that initiated the session, the session is deleted.
+.Sp
+Defaults to false.
+.IP "verify_user_agent" 4
+.IX Item "verify_user_agent"
+When true, \f(CW\*(C`<$c\-\*(C'\fRrequest\->user_agent>> will be checked at prepare time. If it
+is not the same as the user agent that initiated the session, the session is
+deleted.
+.Sp
+Defaults to false.
+.IP "flash_to_stash" 4
+.IX Item "flash_to_stash"
+This option makes it easier to have actions behave the same whether they were
+forwarded to or redirected to. On prepare time it copies the contents of
+\&\f(CW\*(C`flash\*(C'\fR (if any) to the stash.
+.SH "SPECIAL KEYS"
+.IX Header "SPECIAL KEYS"
+The hash reference returned by \f(CW\*(C`$c\->session\*(C'\fR contains several keys which
+are automatically set:
+.IP "_\|_expires" 4
+.IX Item "__expires"
+This key no longer exists. Use \f(CW\*(C`session_expires\*(C'\fR instead.
+.IP "_\|_updated" 4
+.IX Item "__updated"
+The last time a session was saved to the store.
+.IP "_\|_created" 4
+.IX Item "__created"
+The time when the session was first created.
+.IP "_\|_address" 4
+.IX Item "__address"
+The value of \f(CW\*(C`$c\->request\->address\*(C'\fR at the time the session was created.
+This value is only populated if \f(CW\*(C`verify_address\*(C'\fR is true in the configuration.
+.IP "_\|_user_agent" 4
+.IX Item "__user_agent"
+The value of \f(CW\*(C`$c\->request\->user_agent\*(C'\fR at the time the session was created.
+This value is only populated if \f(CW\*(C`verify_user_agent\*(C'\fR is true in the configuration.
+.SH "CAVEATS"
+.IX Header "CAVEATS"
+.SS "Round the Robin Proxies"
+.IX Subsection "Round the Robin Proxies"
+\&\f(CW\*(C`verify_address\*(C'\fR could make your site inaccessible to users who are behind
+load balanced proxies. Some ISPs may give a different \s-1IP\s0 to each request by the
+same client due to this type of proxying. If addresses are verified these
+users' sessions cannot persist.
+.PP
+To let these users access your site you can either disable address verification
+as a whole, or provide a checkbox in the login dialog that tells the server
+that it's \s-1OK\s0 for the address of the client to change. When the server sees that
+this box is checked it should delete the \f(CW\*(C`_\|_address\*(C'\fR special key from the
+session hash when the hash is first created.
+.SS "Race Conditions"
+.IX Subsection "Race Conditions"
+In this day and age where cleaning detergents and Dutch football (not the
+American kind) teams roam the plains in great numbers, requests may happen
+simultaneously. This means that there is some risk of session data being
+overwritten, like this:
+.IP "1." 4
+request a starts, request b starts, with the same session \s-1ID\s0
+.IP "2." 4
+session data is loaded in request a
+.IP "3." 4
+session data is loaded in request b
+.IP "4." 4
+session data is changed in request a
+.IP "5." 4
+request a finishes, session data is updated and written to store
+.IP "6." 4
+request b finishes, session data is updated and written to store, overwriting
+changes by request a
+.PP
+If this is a concern in your application, a soon-to-be-developed locking
+solution is the only safe way to go. This will have a bigger overhead.
+.PP
+For applications where any given user is only making one request at a time this
+plugin should be safe enough.
+.SH "AUTHORS"
+.IX Header "AUTHORS"
+Andy Grundman
+.PP
+Christian Hansen
+.PP
+Yuval Kogman, \f(CW\*(C`nothingmuch@woobling.org\*(C'\fR
+.PP
+Sebastian Riedel
+.PP
+Tomas Doran (t0m) \f(CW\*(C`bobtfish@bobtfish.net\*(C'\fR (current maintainer)
+.PP
+Sergio Salvi
+.PP
+kmx \f(CW\*(C`kmx@volny.cz\*(C'\fR
+.PP
+Florian Ragwitz (rafl) \f(CW\*(C`rafl@debian.org\*(C'\fR
+.PP
+Kent Fredric (kentnl)
+.PP
+And countless other contributers from #catalyst. Thanks guys!
+.SH "COPYRIGHT & LICENSE"
+.IX Header "COPYRIGHT & LICENSE"
+.Vb 3
+\&    Copyright (c) 2005 the aforementioned authors. All rights
+\&    reserved. This program is free software; you can redistribute
+\&    it and/or modify it under the same terms as Perl itself.
+.Ve