Apache-ASP
view release on metacpan - search on metacpan
view release on metacpan or search on metacpan
lib/Apache/ASP/StateManager.pm view on Meta::CPAN
package Apache::ASP;
# quickly decomped out of Apache::ASP so we could load the routines only
# when we are managing State objects
use Apache::ASP::State;
use strict;
use vars qw(
$CleanupGroups
$SessionIDLength $SessionTimeout $StateManager
$DefaultStateDB $DefaultStateSerializer
);
$SessionTimeout = 20;
$StateManager = 10;
# Some OS's have hashed directory lookups up to 16 bytes, so we leave room
# for .lock extension ... nevermind, security is more important, back to 32
# $SessionIDLength = 11;
$SessionIDLength = 32;
$DefaultStateDB = 'SDBM_File';
$DefaultStateSerializer = 'Data::Dumper';
sub InitState {
my $self = shift;
my $r = $self->{r};
my $global_asa = $self->{GlobalASA};
## STATE INITS
# what percent of the session_timeout's time do we garbage collect
# state files and run programs like Session_OnEnd and Application_OnEnd
$self->{state_manager} = &config($self, 'StateManager', undef, $Apache::ASP::StateManager);
# state is the path where state files are stored, like $Session, $Application, etc.
$self->{state_dir} = &config($self, 'StateDir', undef, $self->{global}.'/.state');
$self->{state_dir} =~ tr///; # untaint
$self->{session_state} = &config($self, 'AllowSessionState', undef, 1);
$self->{state_serialize} = &config($self, 'ApplicationSerialize');
if($self->{state_db} = &config($self, 'StateDB')) {
# StateDB - Check StateDB module support
$Apache::ASP::State::DB{$self->{state_db}} ||
$self->Error("$self->{state_db} is not supported for StateDB, try: " .
join(", ", keys %Apache::ASP::State::DB));
$self->{state_db} =~ /^(.*)$/; # untaint
$self->{state_db} = $1; # untaint
# load the state database module && serializer
$self->LoadModule('StateDB', $self->{state_db});
}
if($self->{state_serializer} = &config($self, 'StateSerializer')) {
$self->{state_serializer} =~ tr///; # untaint
$self->LoadModule('StateSerializer', $self->{state_serializer});
}
# INTERNAL tie to the application internal info
my %Internal;
tie(%Internal, 'Apache::ASP::State', $self, 'internal', 'server')
|| $self->Error("can't tie to internal state");
my $internal = $self->{Internal} = bless \%Internal, 'Apache::ASP::State';
$self->{state_serialize} && $internal->LOCK;
# APPLICATION create application object
$self->{app_state} = &config($self, 'AllowApplicationState', undef, 1);
if($self->{app_state}) {
# load at runtime for CGI environments, preloaded for mod_perl
require Apache::ASP::Application;
($self->{Application} = &Apache::ASP::Application::new($self))
|| $self->Error("can't get application state");
$self->{state_serialize} && $self->{Application}->Lock;
} else {
$self->{dbg} && $self->Debug("no application allowed config");
}
# SESSION if we are tracking state, set up the appropriate objects
my $session;
if($self->{session_state}) {
## SESSION INITS
$self->{cookie_path} = &config($self, 'CookiePath', undef, '/');
$self->{cookie_domain} = &config($self, 'CookieDomain');
$self->{paranoid_session} = &config($self, 'ParanoidSession');
$self->{remote_ip} = eval { $r->connection()->remote_ip() }; # may not exist in Apache 2.4
$self->{remote_ip} ||= eval { $r->useragent_ip() }; # should exist in Apache 2.4, best for end user agent IP address
$self->{remote_ip} ||= eval { $r->connection()->client_ip() }; # if useragent_ip not defined for Apache 2.4, try this one
$self->{session_count} = &config($self, 'SessionCount');
# cookieless session support, cascading values
$self->{session_url_parse_match} = &config($self, 'SessionQueryParseMatch');
$self->{session_url_parse} = $self->{session_url_parse_match} || &config($self, 'SessionQueryParse');
$self->{session_url_match} = $self->{session_url_parse_match} || &config($self, 'SessionQueryMatch');
$self->{session_url} = $self->{session_url_parse} || $self->{session_url_match} || &config($self, 'SessionQuery');
$self->{session_url_force} = &config($self, 'SessionQueryForce');
$self->{session_serialize} = &config($self, 'SessionSerialize');
$self->{secure_session} = &config($self, 'SecureSession');
$self->{http_only_session} = &config($self, 'HTTPOnlySession');
# session timeout in seconds since that is what we work with internally
$self->{session_timeout} = &config($self, 'SessionTimeout', undef, $SessionTimeout) * 60;
$self->{'ua'} = $self->{headers_in}->get('User-Agent') || 'UNKNOWN UA';
# refresh group by some increment smaller than session timeout
# to withstand DoS, bruteforce guessing attacks
# defaults to checking the group once every 2 minutes
$self->{group_refresh} = int($self->{session_timeout} / $self->{state_manager});
# Session state is dependent on internal state
# load at runtime for CGI environments, preloaded for mod_perl
require Apache::ASP::Session;
$session = $self->{Session} = &Apache::ASP::Session::new($self)
|| $self->Die("can't create session");
$self->{state_serialize} && $session->Lock();
} else {
$self->{dbg} && $self->Debug("no sessions allowed config");
}
# update after long state init, possible with SessionSerialize config
$self->{Response}->IsClientConnected();
# POSTPOSE STATE EVENTS, so we can delay the Response object creation
# until after the state objects are created
if($session) {
my $last_session_timeout;
if($session->Started()) {
# we only want one process purging at a time
if($self->{app_state}) {
$internal->LOCK();
if(($last_session_timeout = $internal->{LastSessionTimeout} || 0) < time()) {
$internal->{'LastSessionTimeout'} = $self->{session_timeout} + time;
$internal->UNLOCK();
$self->{Application}->Lock;
my $obj = tied(%{$self->{Application}});
if($self->CleanupGroups('PURGE')) {
$last_session_timeout && $global_asa->ApplicationOnEnd();
$global_asa->ApplicationOnStart();
}
$self->{Application}->UnLock;
}
$internal->UNLOCK();
}
$global_asa->SessionOnStart();
}
if($self->{app_state}) {
# The last session timeout should only be updated every group_refresh period
# another optimization, rand() so not all at once either
$internal->LOCK();
$last_session_timeout ||= $internal->{'LastSessionTimeout'};
if($last_session_timeout < $self->{session_timeout} + time +
(rand() * $self->{group_refresh} / 2))
{
$self->{dbg} && $self->Debug("updating LastSessionTimeout from $last_session_timeout");
$internal->{'LastSessionTimeout'} =
$self->{session_timeout} + time() + $self->{group_refresh};
}
$internal->UNLOCK();
}
}
$self;
}
# Cleanup a state group, by default the group of the current session
# We do this currently in DESTROY, which happens after the current
# script has been executed, so that cleanup doesn't happen until
# after output to user
#
# We always exit unless there is a $Session defined, since we only
# cleanup groups of sessions if sessions are allowed for this script
sub CleanupGroup {
my($self, $group_id, $force) = @_;
return unless $self->{Session};
my $asp = $self; # bad hack for some moved around code
$force ||= 0;
# GET GROUP_ID
my $state;
unless($group_id) {
$state = $self->{Session}{_STATE};
$group_id = $state->GroupId();
}
# we must have a group id to work with
$asp->Error("no group id") unless $group_id;
my $group_key = "GroupId" . $group_id;
# cleanup timed out sessions, from current group
my $internal = $asp->{Internal};
$internal->LOCK();
my $group_check = $internal->{$group_key} || 0;
unless($force || ($group_check < time())) {
$internal->UNLOCK();
return;
}
# set the next group_check, randomize a bit to unclump the group checks,
# for 20 minute session timeout, had rand() / 2 + .5, but it was still
# too clumpy, going with pure rand() now, even if a bit less efficient
my $next_check = int($asp->{group_refresh} * rand()) + 1;
$internal->{$group_key} = time() + $next_check;
$internal->UNLOCK();
## GET STATE for group
$state ||= &Apache::ASP::State::new($asp, $group_id);
my $ids = $state->GroupMembers() || [];
# don't return so we can't delete the empty group later
# return unless scalar(@$ids);
$asp->{dbg} && $asp->Debug("group check $group_id, next in $next_check sec");
my $id = $self->{Session}->SessionID();
view all matches for this distributionview release on metacpan - search on metacpan
( run in 0.532 second using v1.00-cache-2.02-grep-82fe00e-cpan-2c419f77a38b )