2008-05-30 11:40:08 +00:00
< ? php
/**
2013-09-28 01:31:35 +00:00
* Session class for CakePHP .
2008-05-30 11:40:08 +00:00
*
2013-09-28 01:31:35 +00:00
* CakePHP abstracts the handling of sessions .
2008-05-30 11:40:08 +00:00
* There are several convenient methods to access session information .
* This class is the implementation of those methods .
* They are mostly used by the Session Component .
*
2017-06-10 21:33:55 +00:00
* CakePHP ( tm ) : Rapid Development Framework ( https :// cakephp . org )
2017-06-10 22:10:52 +00:00
* Copyright ( c ) Cake Software Foundation , Inc . ( https :// cakefoundation . org )
2008-05-30 11:40:08 +00:00
*
* Licensed under The MIT License
2013-02-08 12:22:51 +00:00
* For full copyright and license information , please see the LICENSE . txt
2008-05-30 11:40:08 +00:00
* Redistributions of files must retain the above copyright notice .
*
2017-06-10 22:10:52 +00:00
* @ copyright Copyright ( c ) Cake Software Foundation , Inc . ( https :// cakefoundation . org )
2017-06-10 21:33:55 +00:00
* @ link https :// cakephp . org CakePHP ( tm ) Project
2011-07-26 06:16:14 +00:00
* @ package Cake . Model . Datasource
2008-10-30 17:30:26 +00:00
* @ since CakePHP ( tm ) v . 0.10 . 0.1222
2017-06-10 22:23:14 +00:00
* @ license https :// opensource . org / licenses / mit - license . php MIT License
2008-05-30 11:40:08 +00:00
*/
2009-04-13 19:47:40 +00:00
2012-03-08 04:26:40 +00:00
App :: uses ( 'Hash' , 'Utility' );
2010-12-15 05:43:05 +00:00
App :: uses ( 'Security' , 'Utility' );
2010-12-04 07:21:42 +00:00
2008-05-30 11:40:08 +00:00
/**
2013-09-28 01:31:35 +00:00
* Session class for CakePHP .
2008-05-30 11:40:08 +00:00
*
2013-09-28 01:31:35 +00:00
* CakePHP abstracts the handling of sessions . There are several convenient methods to access session information .
2008-05-30 11:40:08 +00:00
* This class is the implementation of those methods . They are mostly used by the Session Component .
*
2011-07-26 06:16:14 +00:00
* @ package Cake . Model . Datasource
2008-05-30 11:40:08 +00:00
*/
2010-07-04 10:06:24 +00:00
class CakeSession {
2009-07-24 19:18:37 +00:00
2017-10-06 15:11:09 +00:00
/**
* True if the Session is still valid
*
* @ var bool
*/
public static $valid = false ;
/**
* Error messages for this session
*
* @ var array
*/
public static $error = false ;
/**
* User agent string
*
* @ var string
*/
protected static $_userAgent = '' ;
/**
* Path to where the session is active .
*
* @ var string
*/
public static $path = '/' ;
/**
* Error number of last occurred error
*
* @ var int
*/
public static $lastError = null ;
/**
* Start time for this session .
*
* @ var int
*/
public static $time = false ;
/**
* Cookie lifetime
*
* @ var int
*/
public static $cookieLifeTime ;
/**
* Time when this session becomes invalid .
*
* @ var int
*/
public static $sessionTime = false ;
/**
* Current Session id
*
* @ var string
*/
public static $id = null ;
/**
* Hostname
*
* @ var string
*/
public static $host = null ;
/**
* Session timeout multiplier factor
*
* @ var int
*/
public static $timeout = null ;
/**
* Number of requests that can occur during a session time without the session being renewed .
* This feature is only used when config value `Session.autoRegenerate` is set to true .
*
* @ var int
* @ see CakeSession :: _checkValid ()
*/
public static $requestCountdown = 10 ;
/**
* Whether or not the init function in this class was already called
*
* @ var bool
*/
protected static $_initialized = false ;
/**
* Session cookie name
*
* @ var string
*/
protected static $_cookieName = null ;
2017-11-23 09:02:38 +00:00
/**
* Whether or not to make `_validAgentAndTime` 3. x compatible .
*
* @ var bool
*/
protected static $_useForwardsCompatibleTimeout = false ;
2017-10-06 15:11:09 +00:00
/**
* Whether this session is running under a CLI environment
*
* @ var bool
*/
2017-10-07 09:11:45 +00:00
protected static $_isCLI = false ;
2017-10-06 15:04:53 +00:00
2017-10-06 15:11:09 +00:00
/**
* Pseudo constructor .
*
* @ param string | null $base The base path for the Session
* @ return void
*/
public static function init ( $base = null ) {
static :: $time = time ();
if ( env ( 'HTTP_USER_AGENT' ) && ! static :: $_userAgent ) {
static :: $_userAgent = md5 ( env ( 'HTTP_USER_AGENT' ) . Configure :: read ( 'Security.salt' ));
}
static :: _setPath ( $base );
static :: _setHost ( env ( 'HTTP_HOST' ));
if ( ! static :: $_initialized ) {
register_shutdown_function ( 'session_write_close' );
}
static :: $_initialized = true ;
static :: $_isCLI = ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' );
}
/**
* Setup the Path variable
*
* @ param string | null $base base path
* @ return void
*/
protected static function _setPath ( $base = null ) {
if ( empty ( $base )) {
static :: $path = '/' ;
return ;
}
if ( strpos ( $base , 'index.php' ) !== false ) {
$base = str_replace ( 'index.php' , '' , $base );
}
if ( strpos ( $base , '?' ) !== false ) {
$base = str_replace ( '?' , '' , $base );
}
static :: $path = $base ;
}
/**
* Set the host name
*
* @ param string $host Hostname
* @ return void
*/
protected static function _setHost ( $host ) {
static :: $host = $host ;
if ( strpos ( static :: $host , ':' ) !== false ) {
static :: $host = substr ( static :: $host , 0 , strpos ( static :: $host , ':' ));
}
}
/**
* Starts the Session .
*
* @ return bool True if session was started
*/
public static function start () {
if ( static :: started ()) {
return true ;
}
$id = static :: id ();
static :: _startSession ();
if ( ! $id && static :: started ()) {
static :: _checkValid ();
}
static :: $error = false ;
static :: $valid = true ;
return static :: started ();
}
/**
* Determine if Session has been started .
*
* @ return bool True if session has been started .
*/
public static function started () {
if ( function_exists ( 'session_status' )) {
return isset ( $_SESSION ) && ( session_status () === PHP_SESSION_ACTIVE );
}
return isset ( $_SESSION ) && session_id ();
}
/**
* Returns true if given variable is set in session .
*
* @ param string $name Variable name to check for
* @ return bool True if variable is there
*/
public static function check ( $name ) {
if ( ! static :: _hasSession () || ! static :: start ()) {
return false ;
}
if ( isset ( $_SESSION [ $name ])) {
return true ;
}
return Hash :: get ( $_SESSION , $name ) !== null ;
}
/**
* Returns the session id .
* Calling this method will not auto start the session . You might have to manually
* assert a started session .
*
* Passing an id into it , you can also replace the session id if the session
* has not already been started .
* Note that depending on the session handler , not all characters are allowed
* within the session id . For example , the file session handler only allows
* characters in the range a - z A - Z 0 - 9 , ( comma ) and - ( minus ) .
*
* @ param string | null $id Id to replace the current session id
* @ return string Session id
*/
public static function id ( $id = null ) {
if ( $id ) {
static :: $id = $id ;
session_id ( static :: $id );
}
if ( static :: started ()) {
return session_id ();
}
return static :: $id ;
}
/**
* Removes a variable from session .
*
* @ param string $name Session variable to remove
* @ return bool Success
*/
public static function delete ( $name ) {
if ( static :: check ( $name )) {
static :: _overwrite ( $_SESSION , Hash :: remove ( $_SESSION , $name ));
return ! static :: check ( $name );
}
return false ;
}
/**
* Used to write new data to _SESSION , since PHP doesn ' t like us setting the _SESSION var itself .
*
* @ param array & $old Set of old variables => values
* @ param array $new New set of variable => value
* @ return void
*/
protected static function _overwrite ( & $old , $new ) {
if ( ! empty ( $old )) {
foreach ( $old as $key => $var ) {
if ( ! isset ( $new [ $key ])) {
unset ( $old [ $key ]);
}
}
}
foreach ( $new as $key => $var ) {
$old [ $key ] = $var ;
}
}
/**
* Return error description for given error number .
*
* @ param int $errorNumber Error to set
* @ return string Error as string
*/
protected static function _error ( $errorNumber ) {
if ( ! is_array ( static :: $error ) || ! array_key_exists ( $errorNumber , static :: $error )) {
return false ;
}
return static :: $error [ $errorNumber ];
}
/**
* Returns last occurred error as a string , if any .
*
* @ return mixed Error description as a string , or false .
*/
public static function error () {
if ( static :: $lastError ) {
return static :: _error ( static :: $lastError );
}
return false ;
}
/**
* Returns true if session is valid .
*
* @ return bool Success
*/
public static function valid () {
if ( static :: start () && static :: read ( 'Config' )) {
if ( static :: _validAgentAndTime () && static :: $error === false ) {
static :: $valid = true ;
} else {
static :: $valid = false ;
static :: _setError ( 1 , 'Session Highjacking Attempted !!!' );
}
}
return static :: $valid ;
}
/**
* Tests that the user agent is valid and that the session hasn 't ' timed out ' .
* Since timeouts are implemented in CakeSession it checks the current static :: $time
* against the time the session is set to expire . The User agent is only checked
* if Session . checkAgent == true .
*
* @ return bool
*/
protected static function _validAgentAndTime () {
$userAgent = static :: read ( 'Config.userAgent' );
$time = static :: read ( 'Config.time' );
2017-11-23 09:02:38 +00:00
if ( static :: $_useForwardsCompatibleTimeout ) {
$time += ( Configure :: read ( 'Session.timeout' ) * 60 );
}
2017-10-06 15:11:09 +00:00
$validAgent = (
Configure :: read ( 'Session.checkAgent' ) === false ||
isset ( $userAgent ) && static :: $_userAgent === $userAgent
);
return ( $validAgent && static :: $time <= $time );
}
/**
* Get / Set the user agent
*
* @ param string | null $userAgent Set the user agent
* @ return string Current user agent .
*/
public static function userAgent ( $userAgent = null ) {
if ( $userAgent ) {
static :: $_userAgent = $userAgent ;
}
if ( empty ( static :: $_userAgent )) {
CakeSession :: init ( static :: $path );
}
return static :: $_userAgent ;
}
/**
* Returns given session variable , or all of them , if no parameters given .
*
* @ param string | null $name The name of the session variable ( or a path as sent to Set . extract )
* @ return mixed The value of the session variable , null if session not available ,
* session not started , or provided name not found in the session , false on failure .
*/
public static function read ( $name = null ) {
if ( ! static :: _hasSession () || ! static :: start ()) {
return null ;
}
if ( $name === null ) {
return static :: _returnSessionVars ();
}
$result = Hash :: get ( $_SESSION , $name );
if ( isset ( $result )) {
return $result ;
}
return null ;
}
/**
* Returns all session variables .
*
* @ return mixed Full $_SESSION array , or false on error .
*/
protected static function _returnSessionVars () {
if ( ! empty ( $_SESSION )) {
return $_SESSION ;
}
static :: _setError ( 2 , 'No Session vars set' );
return false ;
}
/**
* Writes value to given session variable name .
*
* @ param string | array $name Name of variable
2019-04-29 08:40:10 +00:00
* @ param mixed $value Value to write
2017-10-06 15:11:09 +00:00
* @ return bool True if the write was successful , false if the write failed
*/
public static function write ( $name , $value = null ) {
if ( ! static :: start ()) {
return false ;
}
$write = $name ;
if ( ! is_array ( $name )) {
$write = array ( $name => $value );
}
foreach ( $write as $key => $val ) {
static :: _overwrite ( $_SESSION , Hash :: insert ( $_SESSION , $key , $val ));
if ( Hash :: get ( $_SESSION , $key ) !== $val ) {
return false ;
}
}
return true ;
}
/**
* Reads and deletes a variable from session .
*
* @ param string $name The key to read and remove ( or a path as sent to Hash . extract ) .
* @ return mixed The value of the session variable , null if session not available ,
* session not started , or provided name not found in the session .
*/
public static function consume ( $name ) {
if ( empty ( $name )) {
return null ;
}
$value = static :: read ( $name );
if ( $value !== null ) {
static :: _overwrite ( $_SESSION , Hash :: remove ( $_SESSION , $name ));
}
return $value ;
}
/**
* Helper method to destroy invalid sessions .
*
* @ return void
*/
public static function destroy () {
if ( ! static :: started ()) {
static :: _startSession ();
}
if ( static :: started ()) {
if ( session_id () && static :: _hasSession ()) {
session_write_close ();
session_start ();
}
session_destroy ();
unset ( $_COOKIE [ static :: _cookieName ()]);
}
$_SESSION = null ;
static :: $id = null ;
static :: $_cookieName = null ;
}
/**
* Clears the session .
*
* Optionally also clears the session id and renews the session .
*
* @ param bool $renew If the session should also be renewed . Defaults to true .
* @ return void
*/
public static function clear ( $renew = true ) {
if ( ! $renew ) {
$_SESSION = array ();
return ;
}
$_SESSION = null ;
static :: $id = null ;
static :: renew ();
}
/**
* Helper method to initialize a session , based on CakePHP core settings .
*
* Sessions can be configured with a few shortcut names as well as have any number of ini settings declared .
*
* @ return void
* @ throws CakeSessionException Throws exceptions when ini_set () fails .
*/
protected static function _configureSession () {
$sessionConfig = Configure :: read ( 'Session' );
if ( isset ( $sessionConfig [ 'defaults' ])) {
$defaults = static :: _defaultConfig ( $sessionConfig [ 'defaults' ]);
if ( $defaults ) {
$sessionConfig = Hash :: merge ( $defaults , $sessionConfig );
}
}
if ( ! isset ( $sessionConfig [ 'ini' ][ 'session.cookie_secure' ]) && env ( 'HTTPS' )) {
$sessionConfig [ 'ini' ][ 'session.cookie_secure' ] = 1 ;
}
if ( isset ( $sessionConfig [ 'timeout' ]) && ! isset ( $sessionConfig [ 'cookieTimeout' ])) {
$sessionConfig [ 'cookieTimeout' ] = $sessionConfig [ 'timeout' ];
}
2017-11-23 09:09:25 +00:00
if ( isset ( $sessionConfig [ 'useForwardsCompatibleTimeout' ]) && $sessionConfig [ 'useForwardsCompatibleTimeout' ]) {
2017-11-23 13:12:13 +00:00
static :: $_useForwardsCompatibleTimeout = true ;
2017-11-23 09:02:38 +00:00
}
2017-10-06 15:11:09 +00:00
if ( ! isset ( $sessionConfig [ 'ini' ][ 'session.cookie_lifetime' ])) {
$sessionConfig [ 'ini' ][ 'session.cookie_lifetime' ] = $sessionConfig [ 'cookieTimeout' ] * 60 ;
}
if ( ! isset ( $sessionConfig [ 'ini' ][ 'session.name' ])) {
$sessionConfig [ 'ini' ][ 'session.name' ] = $sessionConfig [ 'cookie' ];
}
static :: $_cookieName = $sessionConfig [ 'ini' ][ 'session.name' ];
if ( ! empty ( $sessionConfig [ 'handler' ])) {
$sessionConfig [ 'ini' ][ 'session.save_handler' ] = 'user' ;
2018-01-16 17:30:04 +00:00
// In PHP7.2.0+ session.save_handler can't be set to 'user' by the user.
// https://github.com/php/php-src/commit/a93a51c3bf4ea1638ce0adc4a899cb93531b9f0d
if ( version_compare ( PHP_VERSION , '7.2.0' , '>=' )) {
unset ( $sessionConfig [ 'ini' ][ 'session.save_handler' ]);
}
2017-10-06 15:11:09 +00:00
} elseif ( ! empty ( $sessionConfig [ 'session.save_path' ]) && Configure :: read ( 'debug' )) {
if ( ! is_dir ( $sessionConfig [ 'session.save_path' ])) {
mkdir ( $sessionConfig [ 'session.save_path' ], 0775 , true );
}
}
if ( ! isset ( $sessionConfig [ 'ini' ][ 'session.gc_maxlifetime' ])) {
$sessionConfig [ 'ini' ][ 'session.gc_maxlifetime' ] = $sessionConfig [ 'timeout' ] * 60 ;
}
if ( ! isset ( $sessionConfig [ 'ini' ][ 'session.cookie_httponly' ])) {
$sessionConfig [ 'ini' ][ 'session.cookie_httponly' ] = 1 ;
}
// For IE<=8
if ( ! isset ( $sessionConfig [ 'cacheLimiter' ])) {
$sessionConfig [ 'cacheLimiter' ] = 'must-revalidate' ;
}
2019-01-16 20:28:35 +00:00
if ( empty ( $_SESSION ) && ! headers_sent () && ( ! function_exists ( 'session_status' ) || session_status () !== PHP_SESSION_ACTIVE )) {
2017-10-06 15:11:09 +00:00
if ( ! empty ( $sessionConfig [ 'ini' ]) && is_array ( $sessionConfig [ 'ini' ])) {
foreach ( $sessionConfig [ 'ini' ] as $setting => $value ) {
if ( ini_set ( $setting , $value ) === false ) {
throw new CakeSessionException ( __d ( 'cake_dev' , 'Unable to configure the session, setting %s failed.' , $setting ));
}
}
}
}
if ( ! empty ( $sessionConfig [ 'handler' ]) && ! isset ( $sessionConfig [ 'handler' ][ 'engine' ])) {
call_user_func_array ( 'session_set_save_handler' , $sessionConfig [ 'handler' ]);
}
2019-11-26 12:39:22 +00:00
if ( ! empty ( $sessionConfig [ 'handler' ][ 'engine' ]) && ! headers_sent ()) {
2017-10-06 15:11:09 +00:00
$handler = static :: _getHandler ( $sessionConfig [ 'handler' ][ 'engine' ]);
2019-11-26 12:39:22 +00:00
if ( ! function_exists ( 'session_status' ) || session_status () !== PHP_SESSION_ACTIVE ) {
session_set_save_handler (
array ( $handler , 'open' ),
array ( $handler , 'close' ),
array ( $handler , 'read' ),
array ( $handler , 'write' ),
array ( $handler , 'destroy' ),
array ( $handler , 'gc' )
);
}
2017-10-06 15:11:09 +00:00
}
Configure :: write ( 'Session' , $sessionConfig );
2017-11-23 09:06:14 +00:00
static :: $sessionTime = static :: $time ;
if ( ! static :: $_useForwardsCompatibleTimeout ) {
static :: $sessionTime += ( $sessionConfig [ 'timeout' ] * 60 );
2017-11-23 09:02:38 +00:00
}
2017-10-06 15:11:09 +00:00
}
/**
* Get session cookie name .
*
* @ return string
*/
protected static function _cookieName () {
if ( static :: $_cookieName !== null ) {
return static :: $_cookieName ;
}
static :: init ();
static :: _configureSession ();
return static :: $_cookieName = session_name ();
}
/**
* Returns whether a session exists
*
* @ return bool
*/
protected static function _hasSession () {
return static :: started ()
2017-10-07 09:11:45 +00:00
|| ! ini_get ( 'session.use_cookies' )
|| isset ( $_COOKIE [ static :: _cookieName ()])
|| static :: $_isCLI
|| ( ini_get ( 'session.use_trans_sid' ) && isset ( $_GET [ session_name ()]));
2017-10-06 15:11:09 +00:00
}
/**
* Find the handler class and make sure it implements the correct interface .
*
* @ param string $handler Handler name .
* @ return CakeSessionHandlerInterface
* @ throws CakeSessionException
*/
protected static function _getHandler ( $handler ) {
list ( $plugin , $class ) = pluginSplit ( $handler , true );
App :: uses ( $class , $plugin . 'Model/Datasource/Session' );
if ( ! class_exists ( $class )) {
throw new CakeSessionException ( __d ( 'cake_dev' , 'Could not load %s to handle the session.' , $class ));
}
$handler = new $class ();
if ( $handler instanceof CakeSessionHandlerInterface ) {
return $handler ;
}
throw new CakeSessionException ( __d ( 'cake_dev' , 'Chosen SessionHandler does not implement CakeSessionHandlerInterface it cannot be used with an engine key.' ));
}
/**
* Get one of the prebaked default session configurations .
*
* @ param string $name Config name .
* @ return bool | array
*/
protected static function _defaultConfig ( $name ) {
$defaults = array (
'php' => array (
'cookie' => 'CAKEPHP' ,
'timeout' => 240 ,
'ini' => array (
'session.use_trans_sid' => 0 ,
'session.cookie_path' => static :: $path
)
),
'cake' => array (
'cookie' => 'CAKEPHP' ,
'timeout' => 240 ,
'ini' => array (
'session.use_trans_sid' => 0 ,
'url_rewriter.tags' => '' ,
'session.serialize_handler' => 'php' ,
'session.use_cookies' => 1 ,
'session.cookie_path' => static :: $path ,
'session.save_path' => TMP . 'sessions' ,
'session.save_handler' => 'files'
)
),
'cache' => array (
'cookie' => 'CAKEPHP' ,
'timeout' => 240 ,
'ini' => array (
'session.use_trans_sid' => 0 ,
'url_rewriter.tags' => '' ,
'session.use_cookies' => 1 ,
'session.cookie_path' => static :: $path ,
'session.save_handler' => 'user' ,
),
'handler' => array (
'engine' => 'CacheSession' ,
'config' => 'default'
)
),
'database' => array (
'cookie' => 'CAKEPHP' ,
'timeout' => 240 ,
'ini' => array (
'session.use_trans_sid' => 0 ,
'url_rewriter.tags' => '' ,
'session.use_cookies' => 1 ,
'session.cookie_path' => static :: $path ,
'session.save_handler' => 'user' ,
'session.serialize_handler' => 'php' ,
),
'handler' => array (
'engine' => 'DatabaseSession' ,
'model' => 'Session'
)
)
);
if ( isset ( $defaults [ $name ])) {
return $defaults [ $name ];
}
return false ;
}
/**
* Helper method to start a session
*
* @ return bool Success
*/
protected static function _startSession () {
static :: init ();
session_write_close ();
static :: _configureSession ();
if ( headers_sent ()) {
if ( empty ( $_SESSION )) {
$_SESSION = array ();
}
} else {
$limit = Configure :: read ( 'Session.cacheLimiter' );
if ( ! empty ( $limit )) {
session_cache_limiter ( $limit );
}
session_start ();
}
return true ;
}
/**
* Helper method to create a new session .
*
* @ return void
*/
protected static function _checkValid () {
$config = static :: read ( 'Config' );
if ( $config ) {
$sessionConfig = Configure :: read ( 'Session' );
if ( static :: valid ()) {
static :: write ( 'Config.time' , static :: $sessionTime );
if ( isset ( $sessionConfig [ 'autoRegenerate' ]) && $sessionConfig [ 'autoRegenerate' ] === true ) {
$check = $config [ 'countdown' ];
$check -= 1 ;
static :: write ( 'Config.countdown' , $check );
if ( $check < 1 ) {
static :: renew ();
static :: write ( 'Config.countdown' , static :: $requestCountdown );
}
}
} else {
$_SESSION = array ();
static :: destroy ();
static :: _setError ( 1 , 'Session Highjacking Attempted !!!' );
static :: _startSession ();
static :: _writeConfig ();
}
} else {
static :: _writeConfig ();
}
}
/**
* Writes configuration variables to the session
*
* @ return void
*/
protected static function _writeConfig () {
static :: write ( 'Config.userAgent' , static :: $_userAgent );
static :: write ( 'Config.time' , static :: $sessionTime );
static :: write ( 'Config.countdown' , static :: $requestCountdown );
}
/**
* Restarts this session .
*
* @ return void
*/
public static function renew () {
if ( session_id () === '' ) {
return ;
}
if ( isset ( $_COOKIE [ static :: _cookieName ()])) {
setcookie ( Configure :: read ( 'Session.cookie' ), '' , time () - 42000 , static :: $path );
}
if ( ! headers_sent ()) {
session_write_close ();
session_start ();
session_regenerate_id ( true );
}
}
/**
* Helper method to set an internal error message .
*
* @ param int $errorNumber Number of the error
* @ param string $errorMessage Description of the error
* @ return void
*/
protected static function _setError ( $errorNumber , $errorMessage ) {
if ( static :: $error === false ) {
static :: $error = array ();
}
static :: $error [ $errorNumber ] = $errorMessage ;
static :: $lastError = $errorNumber ;
}
2012-03-04 19:18:04 +00:00
2010-07-25 15:42:05 +00:00
}