<?php
/**
* a session handler intended as a native PHP replacement
* for the default file handler.
*
* NB this is not *completely* compatible
* - gc works on multi level directories
* - the default umask rather the permissions in session.save_path are applied to new files
*/
class compatSessionHandler extends stackSess {
private $fileHandle;
private $fileName;
private $lastSessionId;
private $levels;
private $mode;
private $lockedOK;
private $overridePath;
function close ()
{
$result_here=false;
$result_there=!$this->shStackNext;
$this->logit("compatSessionHandler::close()");
$result_here=fclose($this->fileHandle);
if ($this->shStackNext) {
$result_there=$this->shStackNext->close();
}
return ($result_here && $result_there);
}
function destroy($session_id)
{
$result_here=false;
$result_there=!$this->shStackNext;
$this->logit("compatSessionHandler::destroy('$session_id')");
$fname=$this->getFileName($session_id);
if (is_file($fname)) {
$result_here=unlink($fname);
}
if ($this->shStackNext) {
$result_there=$this->shStackNext->destroy($session_id);
}
return ($result_here && $result_there);
}
/**
* Note that unlike the default handler
* gc works on nested session directories
* as a result, gc may block for a long time
*/
function gc($maxlifetime)
{
$result_here=false;
$result_there=!$this->shStackNext;
$this->logit("compatSessionHandler::gc($maxlifetime)");
$parts=ini_get('session.save_path');
$parts=explode(";",$parts);
$path=array_pop($parts);
$path=$this->overridePath ? $this->overridePath : $path;
$result_here=$this->gcDir($path, $maxlifetime,$this->levels);
if ($this->shStackNext) {
$result_there=$this->shStackNext->gc($maxlifetime);
}
return ($result_here && $result_there);
}
/**
* internal function used to recurse directories
*/
function gcDir($path, $maxlifetime,$levels)
{
$dh=opendir($path);
if (!$dh) {
return false;
}
$oldestAllowed=time()-$maxlifetime;
while (($file = readdir($dh)) !== false) {
if ('.'===$file) continue;
if ('..'===$file) continue;
$fullPath=$path . DIRECTORY_SEPARATOR . $file;
if (is_dir($fullPath) && $levels>=0) {
if (!$this->gcDir($fullPath,$maxlifetime,$levels-1)) {
closedir($dh);
return false;
}
} else if ($oldestAllowed>filemtime($fullPath)) {
if (!unlink($fullPath)) {
closedir($dh);
return false;
}
}
}
closedir($dh);
return true;
}
function open($save_path, $name)
{
$this->logit("compatSessionHandler::open($save_path,$name)");
// wtf is this intended to do? read is done seperately?
// there's no point checking the directory is writeable/creating a file
// until we know what the session id is (in case of subdirs)
// although save_path is passed, it makes no sense when applied to multi
$this->overridePath=$save_path;
$result=true;
if ($this->shStackNext) {
$result=$this->shStackNext->open($save_path, $name);
}
return $result;
}
function read($session_id)
{
$this->logit("compatSessionHandler::read($session_id)");
$this->lastSessionId=$session_id;
$fname=$this->getFileName($session_id);
$this->logit("* $fname " . ( file_exists($fname) ? " exists" : " does not exist"));
$filemode=file_exists($fname) ? 'r+' : 'w';
$this->lastAccess=file_exists($fname) ? filemtime($fname) : 0;
$session_str='';
// NB there's no calls passed down the stack - what if we had different
// session strings?
if ($this->fileHandle=fopen($fname,$filemode)) {
$this->logit("openmode=$filemode handle=" . $this->fileHandle);
if (flock($this->fileHandle, LOCK_EX)) {
$this->lockedOK=true;
while ('r+'==$filemode && !feof($this->fileHandle)) {
$session_str.=fread($this->fileHandle, 8096);
}
return $session_str;
}
$this->logit("Failed to lock session file in compatSessionHandler::read()");
} else {
$this->logit("Failed to open session file in compatSessionHandler::read()");
}
return '';
}
function write($session_id, $session_data)
{
$this->logit("compatSessionHandler:: write($session_id, \$session_data)");
$result_here=false;
$result_there=!$this->shStackNext;
// if session id is regenerated we are still pointing to the old file!
if ($session_id!=$this->lastSessionId) {
$this->logit("* in compatSessionHandler::write writing diffrent session than opened");
$this->close();
$this->fileName=false; // clear the cached filename
$this->read($session_id); // initialize file and filehandle
} else {
$this->logit("* in compatSessionHandler::write id unchanged");
}
// although we don't read 2 session strings, we might want to write it twice
// e.g. for replication
if ($this->lockedOK) {
ftruncate($this->fileHandle,0);
rewind($this->fileHandle);
$result_here=fwrite($this->fileHandle, $session_data) ? true : false;
$this->logit("* write result = $result_here");
} else {
$this->logit("compatSessionHandler:: write failed");
}
if ($this->shStackNext) {
$result_there=$this->shStackNext->write($session_id, $session_data);
}
return ($result_here && $result_there);
}
/**
* Note that create_sid was added in v5.5.0
* and is required for session data encryption
* NB, this is propogated but *only* one handler should
* create the sid
*/
function create_sid($newlyCreatedSid=false)
{
if (!$newlyCreatedSid) {
if (function_exists('openssl_random_pseudo_bytes')) {
$newlyCreatedSid=bin2hex(openssl_random_pseudo_bytes(16));
} else {
$newlyCreatedSid=md5(uniqid('',true));
}
}
if (is_callable(array($this->shStackNext,'create_sid'))) {
// send notification ONLY down stack
$this->shStackNext->create_sid($newlyCreatedSid);
}
return $newlyCreatedSid;
}
function addNext($slavedHandler)
{
trigger_error("The compatiblity session handler must be at the bottom of the stack", E_USER_ERROR);
return false;
}
function getFileName($session_id)
{
if ($session_id==$this->lastSessionId && $this->fileName) {
return $this->fileName;
}
$parts=ini_get('session.save_path');
$parts=explode(";",$parts);
$path=array_pop($parts);
$path=$this->overridePath ? $this->overridePath : $path;
if (count($path)) {
$this->levels=array_shift($parts);
} else {
$this->levels=0;
}
if (count($path)) {
$this->mode=array_shift($parts);
} else {
$this->mode='0600';
}
$levels=$this->levels;
$offset=0;
while ($levels-$offset) {
$path.=DIRECTORY_SEPARATOR . substr($session_id, $offset,1);
$offset++;
}
$this->lastSessionId=$session_id;
$this->fileName=$path . DIRECTORY_SEPARATOR . 'sess_' . $session_id;
return $this->fileName;
}
function lastAccessed($lastAccess=false)
{
if (is_callable(array($this->shStackNext,'lastAccessed'))) {
$lastAccess=$this->shStackNext->lastAccessed($lastAccess);
}
if (!$lastAccess || $lastAccess>$this->lastAccess) {
return $this->lastAccess;
}
return $lastAccess;
}
}
|