. */ //! Cache-based session handler class Session extends Magic { protected //! Session ID $sid, //! Anti-CSRF token $_csrf, //! User agent $_agent, //! IP, $_ip, //! Suspect callback $onsuspect, //! Cache instance $_cache, //! Session meta data $_data=[]; /** * Open session * @return TRUE * @param $path string * @param $name string **/ function open($path,$name) { return TRUE; } /** * Close session * @return TRUE **/ function close() { $this->sid=NULL; $this->_data=[]; return TRUE; } /** * Return session data in serialized format * @return string * @param $id string **/ function read($id) { $this->sid=$id; if (!$data=$this->_cache->get($id.'.@')) return ''; $this->_data = $data; if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) { $fw=Base::instance(); if (!isset($this->onsuspect) || $fw->call($this->onsuspect,[$this,$id])===FALSE) { //NB: `session_destroy` can't be called at that stage (`session_start` not completed) $this->destroy($id); $this->close(); unset($fw->{'COOKIE.'.session_name()}); $fw->error(403); } } return $data['data']; } /** * Write session data * @return TRUE * @param $id string * @param $data string **/ function write($id,$data) { $fw=Base::instance(); $jar=$fw->JAR; $this->_cache->set($id.'.@', [ 'data'=>$data, 'ip'=>$this->_ip, 'agent'=>$this->_agent, 'stamp'=>time() ], $jar['expire'] ); return TRUE; } /** * Destroy session * @return TRUE * @param $id string **/ function destroy($id) { $this->_cache->clear($id.'.@'); return TRUE; } /** * Garbage collector * @return TRUE * @param $max int **/ function cleanup($max) { $this->_cache->reset('.@',$max); return TRUE; } /** * Return session id (if session has started) * @return string|NULL **/ function sid() { return $this->sid; } /** * Return anti-CSRF token * @return string **/ function csrf() { return $this->_csrf; } /** * Return IP address * @return string **/ function ip() { return $this->_ip; } /** * Return Unix timestamp * @return string|FALSE **/ function stamp() { if (!$this->sid) session_start(); return $this->_cache->exists($this->sid.'.@',$data)? $data['stamp']:FALSE; } /** * Return HTTP user agent * @return string **/ function agent() { return $this->_agent; } /** * Instantiate class * @param $onsuspect callback * @param $key string **/ function __construct($onsuspect=NULL,$key=NULL,$cache=null) { $this->onsuspect=$onsuspect; $this->_cache=$cache?:Cache::instance(); session_set_save_handler( [$this,'open'], [$this,'close'], [$this,'read'], [$this,'write'], [$this,'destroy'], [$this,'cleanup'] ); register_shutdown_function('session_commit'); $fw=\Base::instance(); $headers=$fw->HEADERS; $this->_csrf=$fw->hash($fw->SEED. extension_loaded('openssl')? implode(unpack('L',openssl_random_pseudo_bytes(4))): mt_rand() ); if ($key) $fw->$key=$this->_csrf; $this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:''; $this->_ip=$fw->IP; } /** * check latest meta data existence * @param string $key * @return bool */ function exists($key) { return isset($this->_data[$key]); } /** * get meta data from latest session * @param string $key * @return mixed */ function &get($key) { return $this->_data[$key]; } function set($key,$val) { trigger_error('Unable to set data on previous session'); } function clear($key) { trigger_error('Unable to clear data on previous session'); } }