| 
<?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;
 }
 }
 
 |