You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PlexShare/fuel/core/classes/session/driver.php

713 lines
16 KiB

<?php
/**
* Part of the Fuel framework.
*
* @package Fuel
* @version 1.8
* @author Fuel Development Team
* @license MIT License
* @copyright 2010 - 2016 Fuel Development Team
* @link http://fuelphp.com
*/
namespace Fuel\Core;
abstract class Session_Driver
{
/*
* @var session class configuration
*/
protected $config = array();
/*
* @var session identification keys
*/
protected $keys = array();
/*
* @var session variable data
*/
protected $data = array();
/*
* @var session flash data
*/
protected $flash = array();
/*
* @var session time object
*/
protected $time = null;
// --------------------------------------------------------------------
// abstract methods
// --------------------------------------------------------------------
/**
* create a new session
*
* @access public
* @return void
*/
abstract function create();
// --------------------------------------------------------------------
// generic driver methods
// --------------------------------------------------------------------
/**
* destroy the current session
*
* @return \Session_Driver
*/
public function destroy()
{
// delete the session cookie
\Cookie::delete($this->config['cookie_name'], $this->config['cookie_path'], $this->config['cookie_domain'], null, $this->config['cookie_http_only']);
// reset the stored session data
$this->keys = $this->flash = $this->data = array();
return $this;
}
/**
* read the session
*
* @return \Session_Driver
*/
public function read()
{
// do we need to create a new session?
empty($this->keys) and $this->create();
// mark the loaded flash data, auto-expire if configured
foreach($this->flash as $key => $value)
{
if ($this->config['flash_auto_expire'] === true)
{
$this->flash[$key]['state'] = 'expire';
}
else
{
$this->flash[$key]['state'] = 'loaded';
}
}
return $this;
}
// --------------------------------------------------------------------
/**
* write the session
*
* @return \Session_Driver
*/
public function write()
{
// create the session if it doesn't exist
empty($this->keys) and $this->create();
$this->_cleanup_flash();
return $this;
}
// --------------------------------------------------------------------
/**
* generic driver initialisation
*
* @return void
*/
public function init()
{
// get a time object
$this->time = \Date::time();
}
// --------------------------------------------------------------------
/**
* set session variables
*
* @param string|array $name name of the variable to set or array of values, array(name => value)
* @param mixed $value value
* @return \Session_Driver
*/
public function set($name, $value = null)
{
is_null($name) or \Arr::set($this->data, $name, $value);
return $this;
}
// --------------------------------------------------------------------
/**
* get session variables
*
* @param string $name name of the variable to get
* @param mixed $default default value to return if the variable does not exist
* @return mixed
*/
public function get($name, $default = null)
{
if (is_null($name))
{
return $this->data;
}
return \Arr::get($this->data, $name, $default);
}
// --------------------------------------------------------------------
/**
* get session key variables
*
* @param string $name name of the variable to get, default is 'session_id'
* @return mixed contents of the requested variable, or false if not found
*/
public function key($name = 'session_id')
{
return isset($this->keys[$name]) ? $this->keys[$name] : false;
}
// --------------------------------------------------------------------
/**
* delete session variables
*
* @param string $name name of the variable to delete
* @return \Session_Driver
*/
public function delete($name)
{
\Arr::delete($this->data, $name);
return $this;
}
// --------------------------------------------------------------------
/**
* force a session_id rotation
*
* @param bool $force if true, force a session id rotation
* @return \Session_Driver
*/
public function rotate($force = true)
{
// do we have a session?
if ( ! empty($this->keys))
{
// existing session. need to rotate the session id?
if ($force or ($this->config['rotation_time'] and $this->keys['created'] + $this->config['rotation_time'] <= $this->time->get_timestamp()))
{
// generate a new session id, and update the create timestamp
$this->keys['previous_id'] = $this->keys['session_id'];
$this->keys['session_id'] = $this->_new_session_id();
$this->keys['created'] = $this->time->get_timestamp();
$this->keys['updated'] = $this->keys['created'];
}
}
return $this;
}
// --------------------------------------------------------------------
/**
* set session flash variables
*
* @param string $name name of the variable to set
* @param mixed $value value
* @return \Session_Driver
*/
public function set_flash($name, $value)
{
if (strpos($name, '.') !== false)
{
$keys = explode('.', $name, 2);
$name = array_shift($keys);
}
else
{
$keys = false;
}
if ($keys)
{
if (isset($this->flash[$this->config['flash_id'].'::'.$name]['value']))
{
$this->flash[$this->config['flash_id'].'::'.$name]['state'] = 'new';
}
else
{
$this->flash[$this->config['flash_id'].'::'.$name] = array('state' => 'new', 'value' => array());
}
\Arr::set($this->flash[$this->config['flash_id'].'::'.$name]['value'], $keys[0], $value);
}
else
{
$this->flash[$this->config['flash_id'].'::'.$name] = array('state' => 'new', 'value' => $value);
}
return $this;
}
// --------------------------------------------------------------------
/**
* get session flash variables
*
* @param string $name name of the variable to get
* @param mixed $default default value to return if the variable does not exist
* @param bool $expire true if the flash variable needs to expire immediately, false to use "flash_auto_expire"
* @return mixed
*/
public function get_flash($name, $default = null, $expire = null)
{
// if no expiration is given, use the config default
is_bool($expire) or $expire = $this->config['flash_expire_after_get'];
if (is_null($name))
{
$default = array();
foreach($this->flash as $key => $value)
{
$key = substr($key, strpos($key, '::')+2);
$default[$key] = $value;
}
}
else
{
// check if we need to run an Arr:get()
if (strpos($name, '.') !== false)
{
$keys = explode('.', $name, 2);
$name = array_shift($keys);
}
else
{
$keys = false;
}
if (isset($this->flash[$this->config['flash_id'].'::'.$name]))
{
// if it's not a var set in this request, mark it for expiration
if ($this->flash[$this->config['flash_id'].'::'.$name]['state'] !== 'new' or $expire)
{
$this->flash[$this->config['flash_id'].'::'.$name]['state'] = 'expire';
}
if ($keys)
{
$default = \Arr::get($this->flash[$this->config['flash_id'].'::'.$name]['value'], $keys[0], $default);
}
else
{
$default = $this->flash[$this->config['flash_id'].'::'.$name]['value'];
}
}
}
return ($default instanceof \Closure) ? $default() : $default;
}
// --------------------------------------------------------------------
/**
* keep session flash variables
*
* @param string $name name of the variable to keep
* @return \Session_Driver
*/
public function keep_flash($name)
{
if (is_null($name))
{
foreach($this->flash as $key => $value)
{
$this->flash[$key]['state'] = 'new';
}
}
elseif (isset($this->flash[$this->config['flash_id'].'::'.$name]))
{
$this->flash[$this->config['flash_id'].'::'.$name]['state'] = 'new';
}
return $this;
}
// --------------------------------------------------------------------
/**
* delete session flash variables
*
* @param string $name name of the variable to delete
* @return \Session_Driver
*/
public function delete_flash($name)
{
if (is_null($name))
{
$this->flash = array();
}
elseif (isset($this->flash[$this->config['flash_id'].'::'.$name]))
{
unset($this->flash[$this->config['flash_id'].'::'.$name]);
}
return $this;
}
// --------------------------------------------------------------------
/**
* set the session flash id
*
* @param string $name name of the id to set
* @return \Session_Driver
*/
public function set_flash_id($name)
{
$this->config['flash_id'] = (string) $name;
return $this;
}
// --------------------------------------------------------------------
/**
* get the current session flash id
*
* @return string name of the flash id
*/
public function get_flash_id()
{
return $this->config['flash_id'];
}
// --------------------------------------------------------------------
/**
* get a runtime config value
*
* @param string $name name of the config variable to get
* @return mixed
*/
public function get_config($name)
{
return isset($this->config[$name]) ? $this->config[$name] : null;
}
// --------------------------------------------------------------------
/**
* set a runtime config value
*
* @param string $name name of the config variable to set
* @param mixed $value
* @return \Session_Driver
*/
public function set_config($name, $value = null)
{
if (isset($this->config[$name]))
{
$this->config[$name] = $value;
}
return $this;
}
// --------------------------------------------------------------------
/**
* removes flash variables marked as old
*
* @return void
*/
protected function _cleanup_flash()
{
foreach($this->flash as $key => $value)
{
if ($value['state'] === 'expire')
{
unset($this->flash[$key]);
}
}
}
// --------------------------------------------------------------------
/**
* generate a new session id
*
* @return string
*/
protected function _new_session_id()
{
return substr(\Security::generate_token(), 0, 32);
}
// --------------------------------------------------------------------
/**
* write a cookie
*
* @param array $payload cookie payload
* @return mixed
* @throws \FuelException
*/
protected function _set_cookie($payload = array())
{
if ($this->config['enable_cookie'])
{
$payload = $this->_serialize($payload);
// encrypt the payload if needed
$this->config['encrypt_cookie'] and $payload = \Crypt::encode($payload);
// make sure it doesn't exceed the cookie size specification
if (strlen($payload) > 4000)
{
throw new \FuelException('The session data stored by the application in the cookie exceeds 4Kb. Select a different session storage driver.');
}
// write the session cookie
if ($this->config['expire_on_close'])
{
return \Cookie::set($this->config['cookie_name'], $payload, 0, $this->config['cookie_path'], $this->config['cookie_domain'], null, $this->config['cookie_http_only']);
}
else
{
return \Cookie::set($this->config['cookie_name'], $payload, $this->config['expiration_time'], $this->config['cookie_path'], $this->config['cookie_domain'], null, $this->config['cookie_http_only']);
}
}
}
// --------------------------------------------------------------------
/**
* read a cookie
*
* @return mixed
*/
protected function _get_cookie()
{
// was the cookie value posted?
$cookie = \Input::post($this->config['post_cookie_name'], false);
// if not found, fetch the regular cookie
if ($cookie === false)
{
$cookie = \Cookie::get($this->config['cookie_name'], false);
}
// if not found, was a session-id present in the HTTP header?
if ($cookie === false)
{
$cookie = \Input::headers($this->config['http_header_name'], false);
}
// if not found, check the URL for a cookie
if ($cookie === false)
{
$cookie = \Input::get($this->config['cookie_name'], false);
}
if ($cookie !== false)
{
// fetch the payload
$this->config['encrypt_cookie'] and $cookie = \Crypt::decode($cookie);
$cookie = $this->_unserialize($cookie);
// validate the cookie format: must be an array
if (is_array($cookie))
{
// cookies use nested arrays, other drivers have a string value
if (($this->config['driver'] === 'cookie' and ! is_array($cookie[0])) or
($this->config['driver'] !== 'cookie' and ! is_string($cookie[0])))
{
// invalid specific format
logger('DEBUG', 'Error: Invalid session cookie specific format');
$cookie = false;
}
}
// or a string containing the session id
elseif (is_string($cookie) and strlen($cookie) == 32)
{
$cookie = array($cookie);
}
// invalid general format
else
{
logger('DEBUG', 'Error: Invalid session cookie general format');
$cookie = false;
}
}
// and the result
return $cookie;
}
// --------------------------------------------------------------------
/**
* Serialize an array
*
* This function first converts any slashes found in the array to a temporary
* marker, so when it gets unserialized the slashes will be preserved
*
* @param array $data
* @return string
*/
protected function _serialize($data)
{
if (is_array($data))
{
foreach ($data as $key => $val)
{
if (is_string($val))
{
$data[$key] = str_replace('\\', '{{slash}}', $val);
}
}
}
else
{
if (is_string($data))
{
$data = str_replace('\\', '{{slash}}', $data);
}
}
return serialize($data);
}
// --------------------------------------------------------------------
/**
* Unserialize
*
* This function unserializes a data string, then converts any
* temporary slash markers back to actual slashes
*
* @param array $input
* @return string
*/
protected function _unserialize($input)
{
$data = @unserialize($input);
if (is_array($data))
{
foreach ($data as $key => $val)
{
if (is_string($val))
{
$data[$key] = str_replace('{{slash}}', '\\', $val);
}
}
return $data;
}
elseif ($data === false)
{
is_string($input) and $data = array($input);
}
return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
}
// --------------------------------------------------------------------
/**
* validate__config
*
* This function validates all global (driver independent) configuration values
*
* @param array $config
* @return array
*/
protected function _validate_config($config)
{
$validated = array();
foreach ($config as $name => $item)
{
switch($name)
{
case 'driver':
// if we get here, this one was ok... ;-)
break;
case 'match_ip':
case 'match_ua':
case 'enable_cookie':
case 'cookie_http_only':
case 'encrypt_cookie':
case 'expire_on_close':
case 'flash_expire_after_get':
case 'flash_auto_expire':
case 'native_emulation':
// make sure it's a boolean
$item = (bool) $item;
break;
case 'post_cookie_name':
case 'http_header_name':
case 'cookie_domain':
// make sure it's a string
$item = (string) $item;
break;
case 'cookie_path':
// make sure it's a string
$item = (string) $item;
empty($item) and $item = '/';
break;
case 'expiration_time':
// make sure it's an integer
$item = (int) $item;
// invalid? set it to two years from now
$item <= 0 and $item = 86400 * 365 * 2;
break;
case 'rotation_time':
if ($item !== false)
{
// make sure it's an integer
$item = (int) $item;
// invalid? set it to 5 minutes
$item <= 0 and $item = 300;
}
break;
case 'flash_id':
// make sure it's a string
$item = (string) $item;
empty($item) and $item = 'flash';
break;
default:
// ignore this setting
break;
}
// store the validated result
$validated[$name] = $item;
}
return $validated;
}
}