session.php

Go to the documentation of this file.
00001 <?php
00002 /* SVN FILE: $Id: session_8php-source.html 675 2008-12-26 00:27:14Z gwoo $ */
00003 /**
00004  * Session class for Cake.
00005  *
00006  * Cake abstracts the handling of sessions.
00007  * There are several convenient methods to access session information.
00008  * This class is the implementation of those methods.
00009  * They are mostly used by the Session Component.
00010  *
00011  * PHP versions 4 and 5
00012  *
00013  * CakePHP(tm) :  Rapid Development Framework <http://www.cakephp.org/>
00014  * Copyright 2005-2008, Cake Software Foundation, Inc.
00015  *                              1785 E. Sahara Avenue, Suite 490-204
00016  *                              Las Vegas, Nevada 89104
00017  *
00018  * Licensed under The MIT License
00019  * Redistributions of files must retain the above copyright notice.
00020  *
00021  * @filesource
00022  * @copyright       Copyright 2005-2008, Cake Software Foundation, Inc.
00023  * @link                http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
00024  * @package         cake
00025  * @subpackage      cake.cake.libs
00026  * @since           CakePHP(tm) v .0.10.0.1222
00027  * @version         $Revision: 675 $
00028  * @modifiedby      $LastChangedBy: gwoo $
00029  * @lastmodified    $Date: 2008-12-25 16:27:14 -0800 (Thu, 25 Dec 2008) $
00030  * @license         http://www.opensource.org/licenses/mit-license.php The MIT License
00031  */
00032 /**
00033  * Database name for cake sessions.
00034  *
00035  */
00036     if (!defined('CAKE_SESSION_TABLE')) {
00037          define('CAKE_SESSION_TABLE', 'cake_sessions');
00038     }
00039 
00040     if (CAKE_SESSION_SAVE === 'database') {
00041         uses('model' . DS . 'connection_manager');
00042     }
00043     uses('set');
00044 /**
00045  * Session class for Cake.
00046  *
00047  * Cake abstracts the handling of sessions. There are several convenient methods to access session information.
00048  * This class is the implementation of those methods. They are mostly used by the Session Component.
00049  *
00050  * @package     cake
00051  * @subpackage  cake.cake.libs
00052  */
00053 class CakeSession extends Object {
00054 /**
00055  * True if the Session is still valid
00056  *
00057  * @var boolean
00058  * @access public
00059  */
00060     var $valid = false;
00061 /**
00062  * Error messages for this session
00063  *
00064  * @var array
00065  * @access public
00066  */
00067     var $error = false;
00068 /**
00069  * User agent string
00070  *
00071  * @var string
00072  * @access protected
00073  */
00074     var $_userAgent = '';
00075 /**
00076  * Path to where the session is active.
00077  *
00078  * @var string
00079  * @access public
00080  */
00081     var $path = '/';
00082 /**
00083  * Error number of last occurred error
00084  *
00085  * @var integer
00086  * @access public
00087  */
00088     var $lastError = null;
00089 /**
00090  * CAKE_SECURITY setting, "high", "medium", or "low".
00091  *
00092  * @var string
00093  * @access public
00094  */
00095     var $security = null;
00096 /**
00097  * Start time for this session.
00098  *
00099  * @var integer
00100  * @access public
00101  */
00102     var $time = false;
00103 /**
00104  * Time when this session becomes invalid.
00105  *
00106  * @var integer
00107  * @access public
00108  */
00109     var $sessionTime = false;
00110 /**
00111  * Keeps track of keys to watch for writes on
00112  *
00113  * @var array
00114  * @access public
00115  */
00116     var $watchKeys = array();
00117 /**
00118  * Current Session id
00119  *
00120  * @var string
00121  * @access public
00122  */
00123     var $id = null;
00124 /**
00125  * Constructor.
00126  *
00127  * @param string $base The base path for the Session
00128  * @param boolean $start Should session be started right now
00129  * @access public
00130  */
00131     function __construct($base = null, $start = true) {
00132         if (Configure::read('Session.checkAgent') === true) {
00133             if (env('HTTP_USER_AGENT') != null) {
00134                 $this->_userAgent = md5(env('HTTP_USER_AGENT') . CAKE_SESSION_STRING);
00135             }
00136         }
00137         $this->time = time();
00138 
00139         if ($start === true) {
00140             $this->host = env('HTTP_HOST');
00141 
00142             if (empty($base) || strpos($base, '?') === 0 || strpos($base, 'index.php') === 0) {
00143                 $this->path = '/';
00144             } else {
00145                 $this->path = $base;
00146             }
00147 
00148             if (strpos($this->host, ':') !== false) {
00149                 $this->host = substr($this->host, 0, strpos($this->host, ':'));
00150             }
00151 
00152             $this->sessionTime = $this->time + (Security::inactiveMins() * CAKE_SESSION_TIMEOUT);
00153             $this->security = CAKE_SECURITY;
00154         }
00155         parent::__construct();
00156     }
00157 /**
00158  * Starts the Session.
00159  *
00160  * @param string $name Variable name to check for
00161  * @return boolean True if variable is there
00162  * @access public
00163  */
00164     function start() {
00165         if (function_exists('session_write_close')) {
00166             session_write_close();
00167         }
00168         $this->__initSession();
00169         return $this->__startSession();
00170     }
00171 /**
00172  * Determine if Session has been started.
00173  *
00174  * @access public
00175  */
00176     function started(){
00177         if (isset($_SESSION)) {
00178             return true;
00179         }
00180         return false;
00181     }
00182 /**
00183  * Returns true if given variable is set in session.
00184  *
00185  * @param string $name Variable name to check for
00186  * @return boolean True if variable is there
00187  * @access public
00188  */
00189     function check($name) {
00190         $var = $this->__validateKeys($name);
00191         if (empty($var)) {
00192           return false;
00193         }
00194         $result = Set::extract($_SESSION, $var);
00195         return isset($result);
00196     }
00197 /**
00198  *
00199  * @param id $name string
00200  * @return string Session id
00201  * @access public
00202  */
00203     function id($id = null) {
00204         if ($id) {
00205             $this->id = $id;
00206             session_id($this->id);
00207         }
00208         if (isset($_SESSION)) {
00209             return session_id();
00210         } else {
00211             return $this->id;
00212         }
00213     }
00214 /**
00215  * Temp method until we are able to remove the last eval().
00216  * Builds an expression to fetch a session variable with specified name.
00217  *
00218  * @param string $name Name of variable (in dot notation)
00219  * @access private
00220  */
00221     function __sessionVarNames($name) {
00222         if (is_string($name) && preg_match("/^[ 0-9a-zA-Z._-]*$/", $name)) {
00223             if (strpos($name, ".")) {
00224                 $names = explode(".", $name);
00225             } else {
00226                 $names = array($name);
00227             }
00228             $expression = "\$_SESSION";
00229             foreach ($names as $item) {
00230                 $expression .= is_numeric($item) ? "[$item]" : "['$item']";
00231             }
00232             return $expression;
00233         }
00234         $this->__setError(3, "$name is not a string");
00235         return false;
00236     }
00237 /**
00238  * Removes a variable from session.
00239  *
00240  * @param string $name Session variable to remove
00241  * @return boolean Success
00242  * @access public
00243  */
00244     function del($name) {
00245         if ($this->check($name)) {
00246             if ($var = $this->__validateKeys($name)) {
00247                 if (in_array($var, $this->watchKeys)) {
00248                     trigger_error('Deleting session key {' . $var . '}', E_USER_NOTICE);
00249                 }
00250                 $this->__overwrite($_SESSION, Set::remove($_SESSION, $var));
00251                 return ($this->check($var) == false);
00252             }
00253         }
00254         $this->__setError(2, "$name doesn't exist");
00255         return false;
00256     }
00257 /**
00258  * Used to write new data to _SESSION, since PHP doesn't like us setting the _SESSION var itself
00259  *
00260  * @param array $old Set of old variables => values
00261  * @param array $new New set of variable => value
00262  * @access private
00263  */
00264     function __overwrite(&$old, $new) {
00265         if(!empty($old)) {
00266             foreach ($old as $key => $var) {
00267                 if (!isset($new[$key])) {
00268                     unset($old[$key]);
00269                 }
00270             }
00271         }
00272         foreach ($new as $key => $var) {
00273             $old[$key] = $var;
00274         }
00275     }
00276 /**
00277  * Return error description for given error number.
00278  *
00279  * @param integer $errorNumber Error to set
00280  * @return string Error as string
00281  * @access private
00282  */
00283     function __error($errorNumber) {
00284         if (!is_array($this->error) || !array_key_exists($errorNumber, $this->error)) {
00285             return false;
00286         } else {
00287             return $this->error[$errorNumber];
00288         }
00289     }
00290 /**
00291  * Returns last occurred error as a string, if any.
00292  *
00293  * @return mixed Error description as a string, or false.
00294  * @access public
00295  */
00296     function error() {
00297         if ($this->lastError) {
00298             return $this->__error($this->lastError);
00299         } else {
00300             return false;
00301         }
00302     }
00303 /**
00304  * Returns true if session is valid.
00305  *
00306  * @return boolean Success
00307  * @access public
00308  */
00309     function valid() {
00310         if ($this->read('Config')) {
00311             if (Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read("Config.userAgent") && $this->time <= $this->read("Config.time")) {
00312                 if ($this->error === false) {
00313                     $this->valid = true;
00314                 }
00315             } else {
00316                 $this->valid = false;
00317                 $this->__setError(1, "Session Highjacking Attempted !!!");
00318             }
00319         }
00320         return $this->valid;
00321     }
00322 /**
00323  * Returns given session variable, or all of them, if no parameters given.
00324  *
00325  * @param mixed $name The name of the session variable (or a path as sent to Set.extract)
00326  * @return mixed The value of the session variable
00327  * @access public
00328  */
00329     function read($name = null) {
00330         if (is_null($name)) {
00331             return $this->__returnSessionVars();
00332         }
00333         if (empty($name)) {
00334             return false;
00335         }
00336         $result = Set::extract($_SESSION, $name);
00337 
00338         if (!is_null($result)) {
00339             return $result;
00340         }
00341         $this->__setError(2, "$name doesn't exist");
00342         return null;
00343     }
00344 /**
00345  * Returns all session variables.
00346  *
00347  * @return mixed Full $_SESSION array, or false on error.
00348  * @access private
00349  */
00350     function __returnSessionVars() {
00351         if (!empty($_SESSION)) {
00352             return $_SESSION;
00353         }
00354         $this->__setError(2, "No Session vars set");
00355         return false;
00356     }
00357 /**
00358  * Tells Session to write a notification when a certain session path or subpath is written to
00359  *
00360  * @param mixed $var The variable path to watch
00361  * @access public
00362  */
00363     function watch($var) {
00364         $var = $this->__validateKeys($var);
00365         if (empty($var)) {
00366             return false;
00367         }
00368         $this->watchKeys[] = $var;
00369     }
00370 /**
00371  * Tells Session to stop watching a given key path
00372  *
00373  * @param mixed $var The variable path to watch
00374  * @access public
00375  */
00376     function ignore($var) {
00377         $var = $this->__validateKeys($var);
00378         if (!in_array($var, $this->watchKeys)) {
00379             return;
00380         }
00381         foreach ($this->watchKeys as $i => $key) {
00382             if ($key == $var) {
00383                 unset($this->watchKeys[$i]);
00384                 $this->watchKeys = array_values($this->watchKeys);
00385                 return;
00386             }
00387         }
00388     }
00389 /**
00390  * Writes value to given session variable name.
00391  *
00392  * @param mixed $name Name of variable
00393  * @param string $value Value to write
00394  * @return boolean True if the write was successful, false if the write failed
00395  * @access public
00396  */
00397     function write($name, $value) {
00398         $var = $this->__validateKeys($name);
00399 
00400         if (empty($var)) {
00401             return false;
00402         }
00403         if (in_array($var, $this->watchKeys)) {
00404             trigger_error('Writing session key {' . $var . '}: ' . print_r($value), E_USER_NOTICE);
00405         }
00406         $this->__overwrite($_SESSION, Set::insert($_SESSION, $var, $value));
00407         return (Set::extract($_SESSION, $var) === $value);
00408     }
00409 /**
00410  * Helper method to destroy invalid sessions.
00411  *
00412  * @access public
00413  */
00414     function destroy() {
00415         $sessionpath = session_save_path();
00416         if (empty($sessionpath)) {
00417             $sessionpath = "/tmp";
00418         }
00419 
00420         if (isset($_COOKIE[session_name()])) {
00421             setcookie(CAKE_SESSION_COOKIE, '', time() - 42000, $this->path);
00422         }
00423 
00424         $_SESSION = array();
00425         $file = $sessionpath . DS . "sess_" . session_id();
00426         @session_destroy();
00427         @unlink ($file);
00428         $this->__construct($this->path);
00429         $this->renew();
00430     }
00431 /**
00432  * Helper method to initialize a session, based on Cake core settings.
00433  *
00434  * @access private
00435  */
00436     function __initSession() {
00437         switch($this->security) {
00438             case 'high':
00439                 $this->cookieLifeTime = 0;
00440                 if (function_exists('ini_set')) {
00441                     ini_set('session.referer_check', $this->host);
00442                 }
00443             break;
00444             case 'medium':
00445                 $this->cookieLifeTime = 7 * 86400;
00446                 if (function_exists('ini_set')) {
00447                     ini_set('session.referer_check', $this->host);
00448                 }
00449             break;
00450             case 'low':
00451             default:
00452                 $this->cookieLifeTime = 788940000;
00453             break;
00454         }
00455 
00456         switch(CAKE_SESSION_SAVE) {
00457             case 'cake':
00458                 if (!isset($_SESSION)) {
00459                     if (function_exists('ini_set')) {
00460                         ini_set('session.use_trans_sid', 0);
00461                         ini_set('url_rewriter.tags', '');
00462                         ini_set('session.serialize_handler', 'php');
00463                         ini_set('session.use_cookies', 1);
00464                         ini_set('session.name', CAKE_SESSION_COOKIE);
00465                         ini_set('session.cookie_lifetime', $this->cookieLifeTime);
00466                         ini_set('session.cookie_path', $this->path);
00467                         ini_set('session.auto_start', 0);
00468                         ini_set('session.save_path', TMP . 'sessions');
00469                     }
00470                 }
00471             break;
00472             case 'database':
00473                 if (!isset($_SESSION)) {
00474                     if (function_exists('ini_set')) {
00475                         ini_set('session.use_trans_sid', 0);
00476                         ini_set('url_rewriter.tags', '');
00477                         ini_set('session.save_handler', 'user');
00478                         ini_set('session.serialize_handler', 'php');
00479                         ini_set('session.use_cookies', 1);
00480                         ini_set('session.name', CAKE_SESSION_COOKIE);
00481                         ini_set('session.cookie_lifetime', $this->cookieLifeTime);
00482                         ini_set('session.cookie_path', $this->path);
00483                         ini_set('session.auto_start', 0);
00484                     }
00485                 }
00486                 session_set_save_handler(array('CakeSession','__open'),
00487                                                     array('CakeSession', '__close'),
00488                                                     array('CakeSession', '__read'),
00489                                                     array('CakeSession', '__write'),
00490                                                     array('CakeSession', '__destroy'),
00491                                                     array('CakeSession', '__gc'));
00492             break;
00493             case 'php':
00494                 if (!isset($_SESSION)) {
00495                     if (function_exists('ini_set')) {
00496                         ini_set('session.use_trans_sid', 0);
00497                         ini_set('session.name', CAKE_SESSION_COOKIE);
00498                         ini_set('session.cookie_lifetime', $this->cookieLifeTime);
00499                         ini_set('session.cookie_path', $this->path);
00500                     }
00501                 }
00502             break;
00503             default:
00504                 if (!isset($_SESSION)) {
00505                     $config = CONFIGS . CAKE_SESSION_SAVE . '.php';
00506 
00507                     if (is_file($config)) {
00508                         require_once ($config);
00509                     }
00510                 }
00511             break;
00512         }
00513     }
00514 /**
00515  * Helper method to start a session
00516  *
00517  * @access private
00518  */
00519     function __startSession() {
00520         if (headers_sent()) {
00521             if (!isset($_SESSION)) {
00522                 $_SESSION = array();
00523             }
00524             return false;
00525         } elseif (!isset($_SESSION)) {
00526             session_cache_limiter ("must-revalidate");
00527             session_start();
00528             header ('P3P: CP="NOI ADM DEV PSAi COM NAV OUR OTRo STP IND DEM"');
00529             return true;
00530         } else {
00531             session_start();
00532             return true;
00533         }
00534     }
00535 /**
00536  * Helper method to create a new session.
00537  *
00538  * @access protected
00539  */
00540     function _checkValid() {
00541         if ($this->read('Config')) {
00542             if (Configure::read('Session.checkAgent') === false || $this->_userAgent == $this->read("Config.userAgent") && $this->time <= $this->read("Config.time")) {
00543                 $time = $this->read("Config.time");
00544                 $this->write("Config.time", $this->sessionTime);
00545 
00546                 if ($this->security === 'high') {
00547                     $check = $this->read("Config.timeout");
00548                     $check = $check - 1;
00549                     $this->write("Config.timeout", $check);
00550 
00551                     if (time() > ($time - (Security::inactiveMins() * CAKE_SESSION_TIMEOUT) + 2) || $check < 1) {
00552                         $this->renew();
00553                         $this->write('Config.timeout', 10);
00554                     }
00555                 }
00556                 $this->valid = true;
00557             } else {
00558                 $this->destroy();
00559                 $this->valid = false;
00560                 $this->__setError(1, "Session Highjacking Attempted !!!");
00561             }
00562         } else {
00563             srand ((double)microtime() * 1000000);
00564             $this->write("Config.userAgent", $this->_userAgent);
00565             $this->write("Config.time", $this->sessionTime);
00566             $this->write('Config.rand', rand());
00567             $this->write('Config.timeout', 10);
00568             $this->valid = true;
00569             $this->__setError(1, "Session is valid");
00570         }
00571     }
00572 /**
00573  * Helper method to restart a session.
00574  *
00575  * @access private
00576  */
00577     function __regenerateId() {
00578         $oldSessionId = session_id();
00579         if ($oldSessionId) {
00580             $sessionpath = session_save_path();
00581             if (empty($sessionpath)) {
00582                 $sessionpath = "/tmp";
00583             }
00584 
00585             if (isset($_COOKIE[session_name()])) {
00586                 setcookie(CAKE_SESSION_COOKIE, '', time() - 42000, $this->path);
00587             }
00588             session_regenerate_id();
00589             $newSessid = session_id();
00590 
00591             if (function_exists('session_write_close')) {
00592                 session_write_close();
00593             }
00594             $this->__initSession();
00595             session_id($oldSessionId);
00596             session_start();
00597             session_destroy();
00598             $file = $sessionpath . DS . "sess_$oldSessionId";
00599             @unlink($file);
00600             $this->__initSession();
00601             session_id($newSessid);
00602             session_start();
00603         }
00604     }
00605 /**
00606  * Restarts this session.
00607  *
00608  * @access public
00609  */
00610     function renew() {
00611         $this->__regenerateId();
00612     }
00613 /**
00614  * Validate that the $name is in correct dot notation
00615  * example: $name = 'ControllerName.key';
00616  *
00617  * @param string $name Session key names as string.
00618  * @return mixed false is $name is not correct format, or $name if it is correct
00619  * @access private
00620  */
00621     function __validateKeys($name) {
00622         if (is_string($name) && preg_match("/^[ 0-9a-zA-Z._-]*$/", $name)) {
00623             return $name;
00624         }
00625         $this->__setError(3, "$name is not a string");
00626         return false;
00627     }
00628 /**
00629  * Helper method to set an internal error message.
00630  *
00631  * @param integer $errorNumber Number of the error
00632  * @param string $errorMessage Description of the error
00633  * @access private
00634  */
00635     function __setError($errorNumber, $errorMessage) {
00636         if ($this->error === false) {
00637             $this->error = array();
00638         }
00639         $this->error[$errorNumber] = $errorMessage;
00640         $this->lastError = $errorNumber;
00641     }
00642 /**
00643  * Method called on open of a database session.
00644  *
00645  * @return boolean Success
00646  * @access private
00647  */
00648     function __open() {
00649         return true;
00650     }
00651 /**
00652  * Method called on close of a database session.
00653  *
00654  * @return boolean Success
00655  * @access private
00656  */
00657     function __close() {
00658         $probability = mt_rand(1, 150);
00659         if ($probability <= 3) {
00660             CakeSession::__gc();
00661         }
00662         return true;
00663     }
00664 /**
00665  * Method used to read from a database session.
00666  *
00667  * @param mixed $key The key of the value to read
00668  * @return mixed The value of the key or false if it does not exist
00669  * @access private
00670  */
00671     function __read($key) {
00672         $db =& ConnectionManager::getDataSource('default');
00673         $table = $db->fullTableName(CAKE_SESSION_TABLE, false);
00674         $row = $db->query("SELECT " . $db->name($table.'.data') . " FROM " . $db->name($table) . " WHERE " . $db->name($table.'.id') . " = " . $db->value($key), false);
00675 
00676         if ($row && !isset($row[0][$table]) && isset($row[0][0])) {
00677             $table = 0;
00678         }
00679 
00680         if ($row && $row[0][$table]['data']) {
00681             return $row[0][$table]['data'];
00682         } else {
00683             return false;
00684         }
00685     }
00686 /**
00687  * Helper function called on write for database sessions.
00688  *
00689  * @param mixed $key The name of the var
00690  * @param mixed $value The value of the var
00691  * @return boolean Success
00692  * @access private
00693  */
00694     function __write($key, $value) {
00695         $db =& ConnectionManager::getDataSource('default');
00696         $table = $db->fullTableName(CAKE_SESSION_TABLE);
00697 
00698         switch(CAKE_SECURITY) {
00699             case 'high':
00700                 $factor = 10;
00701             break;
00702             case 'medium':
00703                 $factor = 100;
00704             break;
00705             case 'low':
00706                 $factor = 300;
00707             break;
00708             default:
00709                 $factor = 10;
00710             break;
00711         }
00712         $expires = time() + CAKE_SESSION_TIMEOUT * $factor;
00713         $row = $db->query("SELECT COUNT(id) AS count FROM " . $db->name($table) . " WHERE "
00714                                  . $db->name('id') . " = "
00715                                  . $db->value($key), false);
00716 
00717         if ($row[0][0]['count'] > 0) {
00718             $db->execute("UPDATE " . $db->name($table) . " SET " . $db->name('data') . " = "
00719                                 . $db->value($value) . ", " . $db->name('expires') . " = "
00720                                 . $db->value($expires) . " WHERE " . $db->name('id') . " = "
00721                                 . $db->value($key));
00722         } else {
00723             $db->execute("INSERT INTO " . $db->name($table) . " (" . $db->name('data') . ","
00724                                 . $db->name('expires') . "," . $db->name('id')
00725                                 . ") VALUES (" . $db->value($value) . ", " . $db->value($expires) . ", "
00726                                 . $db->value($key) . ")");
00727         }
00728         return true;
00729     }
00730 /**
00731  * Method called on the destruction of a database session.
00732  *
00733  * @param int $key Key that uniquely identifies session in database
00734  * @return boolean Success
00735  * @access private
00736  */
00737     function __destroy($key) {
00738         $db =& ConnectionManager::getDataSource('default');
00739         $table = $db->fullTableName(CAKE_SESSION_TABLE);
00740         $db->execute("DELETE FROM " . $db->name($table) . " WHERE " . $db->name($table.'.id') . " = " . $db->value($key, 'integer'));
00741         return true;
00742     }
00743 /**
00744  * Helper function called on gc for database sessions.
00745  *
00746  * @param int $expires Timestamp (defaults to current time)
00747  * @return boolean Success
00748  * @access private
00749  */
00750     function __gc($expires = null) {
00751         $db =& ConnectionManager::getDataSource('default');
00752         $table = $db->fullTableName(CAKE_SESSION_TABLE);
00753         $db->execute("DELETE FROM " . $db->name($table) . " WHERE " . $db->name($table.'.expires') . " < ". $db->value(time()));
00754         return true;
00755      }
00756 }
00757 ?>