Session.php

Go to the documentation of this file.
00001 <?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
00002 /**
00003  * CodeIgniter
00004  *
00005  * An open source application development framework for PHP 4.3.2 or newer
00006  *
00007  * @package             CodeIgniter
00008  * @author              ExpressionEngine Dev Team
00009  * @copyright   Copyright (c) 2008, EllisLab, Inc.
00010  * @license             http://codeigniter.com/user_guide/license.html
00011  * @link                http://codeigniter.com
00012  * @since               Version 1.0
00013  * @filesource
00014  */
00015 
00016 // ------------------------------------------------------------------------
00017 
00018 /**
00019  * Session Class
00020  *
00021  * @package             CodeIgniter
00022  * @subpackage  Libraries
00023  * @category    Sessions
00024  * @author              ExpressionEngine Dev Team
00025  * @link                http://codeigniter.com/user_guide/libraries/sessions.html
00026  */
00027 class CI_Session {
00028 
00029         var $sess_encrypt_cookie                = FALSE;
00030         var $sess_use_database                  = FALSE;
00031         var $sess_table_name                    = '';
00032         var $sess_expiration                    = 7200;
00033         var $sess_match_ip                              = FALSE;
00034         var $sess_match_useragent               = TRUE;
00035         var $sess_cookie_name                   = 'ci_session';
00036         var $cookie_prefix                              = '';
00037         var $cookie_path                                = '';
00038         var $cookie_domain                              = '';
00039         var $sess_time_to_update                = 300;
00040         var $encryption_key                             = '';
00041         var $flashdata_key                              = 'flash';
00042         var $time_reference                             = 'time';
00043         var $gc_probability                             = 5;
00044         var $userdata                                   = array();
00045         var $CI;
00046         var $now;
00047 
00048         /**
00049          * Session Constructor
00050          *
00051          * The constructor runs the session routines automatically
00052          * whenever the class is instantiated.
00053          */             
00054         function CI_Session($params = array())
00055         {
00056                 log_message('debug', "Session Class Initialized");
00057 
00058                 // Set the super object to a local variable for use throughout the class
00059                 $this->CI =& get_instance();
00060                 
00061                 // Set all the session preferences, which can either be set 
00062                 // manually via the $params array above or via the config file
00063                 foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
00064                 {
00065                         $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
00066                 }               
00067         
00068                 // Load the string helper so we can use the strip_slashes() function
00069                 $this->CI->load->helper('string');
00070 
00071                 // Do we need encryption? If so, load the encryption class
00072                 if ($this->sess_encrypt_cookie == TRUE)
00073                 {
00074                         $this->CI->load->library('encrypt');
00075                 }
00076 
00077                 // Are we using a database?  If so, load it
00078                 if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')
00079                 {
00080                         $this->CI->load->database();
00081                 }
00082 
00083                 // Set the "now" time.  Can either be GMT or server time, based on the
00084                 // config prefs.  We use this to set the "last activity" time
00085                 $this->now = $this->_get_time();
00086 
00087                 // Set the session length. If the session expiration is
00088                 // set to zero we'll set the expiration two years from now.
00089                 if ($this->sess_expiration == 0)
00090                 {
00091                         $this->sess_expiration = (60*60*24*365*2);
00092                 }
00093                                                 
00094                 // Set the cookie name
00095                 $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
00096         
00097                 // Run the Session routine. If a session doesn't exist we'll 
00098                 // create a new one.  If it does, we'll update it.
00099                 if ( ! $this->sess_read())
00100                 {
00101                         $this->sess_create();
00102                 }
00103                 else
00104                 {       
00105                         $this->sess_update();
00106                 }
00107                 
00108                 // Delete 'old' flashdata (from last request)
00109                 $this->_flashdata_sweep();
00110                 
00111                 // Mark all new flashdata as old (data will be deleted before next request)
00112                 $this->_flashdata_mark();
00113 
00114                 // Delete expired sessions if necessary
00115                 $this->_sess_gc();
00116 
00117                 log_message('debug', "Session routines successfully run");
00118         }
00119         
00120         // --------------------------------------------------------------------
00121         
00122         /**
00123          * Fetch the current session data if it exists
00124          *
00125          * @access      public
00126          * @return      void
00127          */
00128         function sess_read()
00129         {       
00130                 // Fetch the cookie
00131                 $session = $this->CI->input->cookie($this->sess_cookie_name);
00132                 
00133                 // No cookie?  Goodbye cruel world!...
00134                 if ($session === FALSE)
00135                 {
00136                         log_message('debug', 'A session cookie was not found.');
00137                         return FALSE;
00138                 }
00139                 
00140                 // Decrypt the cookie data
00141                 if ($this->sess_encrypt_cookie == TRUE)
00142                 {
00143                         $session = $this->CI->encrypt->decode($session);
00144                 }
00145                 else
00146                 {       
00147                         // encryption was not used, so we need to check the md5 hash
00148                         $hash    = substr($session, strlen($session)-32); // get last 32 chars
00149                         $session = substr($session, 0, strlen($session)-32);
00150 
00151                         // Does the md5 hash match?  This is to prevent manipulation of session data in userspace
00152                         if ($hash !==  md5($session.$this->encryption_key))
00153                         {
00154                                 log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
00155                                 $this->sess_destroy();
00156                                 return FALSE;
00157                         }
00158                 }
00159                 
00160                 // Unserialize the session array
00161                 $session = $this->_unserialize($session);
00162                 
00163                 // Is the session data we unserialized an array with the correct format?
00164                 if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))
00165                 {
00166                         $this->sess_destroy();
00167                         return FALSE;
00168                 }
00169                 
00170                 // Is the session current?
00171                 if (($session['last_activity'] + $this->sess_expiration) < $this->now)
00172                 {
00173                         $this->sess_destroy();
00174                         return FALSE;
00175                 }
00176 
00177                 // Does the IP Match?
00178                 if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
00179                 {
00180                         $this->sess_destroy();
00181                         return FALSE;
00182                 }
00183                 
00184                 // Does the User Agent Match?
00185                 if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50)))
00186                 {
00187                         $this->sess_destroy();
00188                         return FALSE;
00189                 }
00190                 
00191                 // Is there a corresponding session in the DB?
00192                 if ($this->sess_use_database === TRUE)
00193                 {
00194                         $this->CI->db->where('session_id', $session['session_id']);
00195                                         
00196                         if ($this->sess_match_ip == TRUE)
00197                         {
00198                                 $this->CI->db->where('ip_address', $session['ip_address']);
00199                         }
00200 
00201                         if ($this->sess_match_useragent == TRUE)
00202                         {
00203                                 $this->CI->db->where('user_agent', $session['user_agent']);
00204                         }
00205                         
00206                         $query = $this->CI->db->get($this->sess_table_name);
00207 
00208                         // No result?  Kill it!
00209                         if ($query->num_rows() == 0)
00210                         {
00211                                 $this->sess_destroy();
00212                                 return FALSE;
00213                         }
00214 
00215                         // Is there custom data?  If so, add it to the main session array
00216                         $row = $query->row();
00217                         if (isset($row->user_data) AND $row->user_data != '')
00218                         {
00219                                 $custom_data = $this->_unserialize($row->user_data);
00220 
00221                                 if (is_array($custom_data))
00222                                 {
00223                                         foreach ($custom_data as $key => $val)
00224                                         {
00225                                                 $session[$key] = $val;
00226                                         }
00227                                 }
00228                         }                               
00229                 }
00230         
00231                 // Session is valid!
00232                 $this->userdata = $session;
00233                 unset($session);
00234                 
00235                 return TRUE;
00236         }
00237         
00238         // --------------------------------------------------------------------
00239         
00240         /**
00241          * Write the session data
00242          *
00243          * @access      public
00244          * @return      void
00245          */
00246         function sess_write()
00247         {
00248                 // Are we saving custom data to the DB?  If not, all we do is update the cookie
00249                 if ($this->sess_use_database === FALSE)
00250                 {
00251                         $this->_set_cookie();
00252                         return;
00253                 }
00254 
00255                 // set the custom userdata, the session data we will set in a second
00256                 $custom_userdata = $this->userdata;
00257                 $cookie_userdata = array();
00258                 
00259                 // Before continuing, we need to determine if there is any custom data to deal with.
00260                 // Let's determine this by removing the default indexes to see if there's anything left in the array
00261                 // and set the session data while we're at it
00262                 foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
00263                 {
00264                         unset($custom_userdata[$val]);
00265                         $cookie_userdata[$val] = $this->userdata[$val];
00266                 }
00267                 
00268                 // Did we find any custom data?  If not, we turn the empty array into a string
00269                 // since there's no reason to serialize and store an empty array in the DB
00270                 if (count($custom_userdata) === 0)
00271                 {
00272                         $custom_userdata = '';
00273                 }
00274                 else
00275                 {       
00276                         // Serialize the custom data array so we can store it
00277                         $custom_userdata = $this->_serialize($custom_userdata);
00278                 }
00279                 
00280                 // Run the update query
00281                 $this->CI->db->where('session_id', $this->userdata['session_id']);
00282                 $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
00283 
00284                 // Write the cookie.  Notice that we manually pass the cookie data array to the
00285                 // _set_cookie() function. Normally that function will store $this->userdata, but 
00286                 // in this case that array contains custom data, which we do not want in the cookie.
00287                 $this->_set_cookie($cookie_userdata);
00288         }
00289         
00290         // --------------------------------------------------------------------
00291         
00292         /**
00293          * Create a new session
00294          *
00295          * @access      public
00296          * @return      void
00297          */
00298         function sess_create()
00299         {       
00300                 $sessid = '';
00301                 while (strlen($sessid) < 32)
00302                 {
00303                         $sessid .= mt_rand(0, mt_getrandmax());
00304                 }
00305                 
00306                 // To make the session ID even more secure we'll combine it with the user's IP
00307                 $sessid .= $this->CI->input->ip_address();
00308         
00309                 $this->userdata = array(
00310                                                         'session_id'    => md5(uniqid($sessid, TRUE)),
00311                                                         'ip_address'    => $this->CI->input->ip_address(),
00312                                                         'user_agent'    => substr($this->CI->input->user_agent(), 0, 50),
00313                                                         'last_activity' => $this->now
00314                                                         );
00315                 
00316                 
00317                 // Save the data to the DB if needed
00318                 if ($this->sess_use_database === TRUE)
00319                 {
00320                         $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
00321                 }
00322                         
00323                 // Write the cookie
00324                 $this->_set_cookie();
00325         }
00326         
00327         // --------------------------------------------------------------------
00328         
00329         /**
00330          * Update an existing session
00331          *
00332          * @access      public
00333          * @return      void
00334          */
00335         function sess_update()
00336         {
00337                 // We only update the session every five minutes by default
00338                 if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
00339                 {
00340                         return;
00341                 }
00342         
00343                 // Save the old session id so we know which record to 
00344                 // update in the database if we need it
00345                 $old_sessid = $this->userdata['session_id'];
00346                 $new_sessid = '';
00347                 while (strlen($new_sessid) < 32)
00348                 {
00349                         $new_sessid .= mt_rand(0, mt_getrandmax());
00350                 }
00351                 
00352                 // To make the session ID even more secure we'll combine it with the user's IP
00353                 $new_sessid .= $this->CI->input->ip_address();
00354                 
00355                 // Turn it into a hash
00356                 $new_sessid = md5(uniqid($new_sessid, TRUE));
00357                 
00358                 // Update the session data in the session data array
00359                 $this->userdata['session_id'] = $new_sessid;
00360                 $this->userdata['last_activity'] = $this->now;
00361                 
00362                 // _set_cookie() will handle this for us if we aren't using database sessions
00363                 // by pushing all userdata to the cookie.
00364                 $cookie_data = NULL;
00365                 
00366                 // Update the session ID and last_activity field in the DB if needed
00367                 if ($this->sess_use_database === TRUE)
00368                 {
00369                         // set cookie explicitly to only have our session data
00370                         $cookie_data = array();
00371                         foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
00372                         {
00373                                 $cookie_data[$val] = $this->userdata[$val];
00374                         }
00375                         
00376                         $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
00377                 }
00378                 
00379                 // Write the cookie
00380                 $this->_set_cookie($cookie_data);
00381         }
00382         
00383         // --------------------------------------------------------------------
00384         
00385         /**
00386          * Destroy the current session
00387          *
00388          * @access      public
00389          * @return      void
00390          */
00391         function sess_destroy()
00392         {       
00393                 // Kill the session DB row
00394                 if ($this->sess_use_database === TRUE AND isset($this->userdata['session_id']))
00395                 {
00396                         $this->CI->db->where('session_id', $this->userdata['session_id']);
00397                         $this->CI->db->delete($this->sess_table_name);
00398                 }
00399         
00400                 // Kill the cookie
00401                 setcookie(
00402                                         $this->sess_cookie_name,
00403                                         addslashes(serialize(array())),
00404                                         ($this->now - 31500000),
00405                                         $this->cookie_path,
00406                                         $this->cookie_domain,
00407                                         0
00408                                 );
00409         }
00410         
00411         // --------------------------------------------------------------------
00412         
00413         /**
00414          * Fetch a specific item from the session array
00415          *
00416          * @access      public
00417          * @param       string
00418          * @return      string
00419          */             
00420         function userdata($item)
00421         {
00422                 return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
00423         }
00424 
00425         // --------------------------------------------------------------------
00426         
00427         /**
00428          * Fetch all session data
00429          *
00430          * @access      public
00431          * @return      mixed
00432          */     
00433         function all_userdata()
00434         {
00435                 return ( ! isset($this->userdata)) ? FALSE : $this->userdata;
00436         }
00437         
00438         // --------------------------------------------------------------------
00439         
00440         /**
00441          * Add or change data in the "userdata" array
00442          *
00443          * @access      public
00444          * @param       mixed
00445          * @param       string
00446          * @return      void
00447          */             
00448         function set_userdata($newdata = array(), $newval = '')
00449         {
00450                 if (is_string($newdata))
00451                 {
00452                         $newdata = array($newdata => $newval);
00453                 }
00454         
00455                 if (count($newdata) > 0)
00456                 {
00457                         foreach ($newdata as $key => $val)
00458                         {
00459                                 $this->userdata[$key] = $val;
00460                         }
00461                 }
00462 
00463                 $this->sess_write();
00464         }
00465         
00466         // --------------------------------------------------------------------
00467         
00468         /**
00469          * Delete a session variable from the "userdata" array
00470          *
00471          * @access      array
00472          * @return      void
00473          */             
00474         function unset_userdata($newdata = array())
00475         {
00476                 if (is_string($newdata))
00477                 {
00478                         $newdata = array($newdata => '');
00479                 }
00480         
00481                 if (count($newdata) > 0)
00482                 {
00483                         foreach ($newdata as $key => $val)
00484                         {
00485                                 unset($this->userdata[$key]);
00486                         }
00487                 }
00488         
00489                 $this->sess_write();
00490         }
00491         
00492         // ------------------------------------------------------------------------
00493 
00494         /**
00495          * Add or change flashdata, only available
00496          * until the next request
00497          *
00498          * @access      public
00499          * @param       mixed
00500          * @param       string
00501          * @return      void
00502          */
00503         function set_flashdata($newdata = array(), $newval = '')
00504         {
00505                 if (is_string($newdata))
00506                 {
00507                         $newdata = array($newdata => $newval);
00508                 }
00509                 
00510                 if (count($newdata) > 0)
00511                 {
00512                         foreach ($newdata as $key => $val)
00513                         {
00514                                 $flashdata_key = $this->flashdata_key.':new:'.$key;
00515                                 $this->set_userdata($flashdata_key, $val);
00516                         }
00517                 }
00518         } 
00519         
00520         // ------------------------------------------------------------------------
00521 
00522         /**
00523          * Keeps existing flashdata available to next request.
00524          *
00525          * @access      public
00526          * @param       string
00527          * @return      void
00528          */
00529         function keep_flashdata($key)
00530         {
00531                 // 'old' flashdata gets removed.  Here we mark all 
00532                 // flashdata as 'new' to preserve it from _flashdata_sweep()
00533                 // Note the function will return FALSE if the $key 
00534                 // provided cannot be found
00535                 $old_flashdata_key = $this->flashdata_key.':old:'.$key;
00536                 $value = $this->userdata($old_flashdata_key);
00537 
00538                 $new_flashdata_key = $this->flashdata_key.':new:'.$key;
00539                 $this->set_userdata($new_flashdata_key, $value);
00540         }
00541         
00542         // ------------------------------------------------------------------------
00543 
00544         /**
00545          * Fetch a specific flashdata item from the session array
00546          *
00547          * @access      public
00548          * @param       string
00549          * @return      string
00550          */     
00551         function flashdata($key)
00552         {
00553                 $flashdata_key = $this->flashdata_key.':old:'.$key;
00554                 return $this->userdata($flashdata_key);
00555         }
00556 
00557         // ------------------------------------------------------------------------
00558 
00559         /**
00560          * Identifies flashdata as 'old' for removal
00561          * when _flashdata_sweep() runs.
00562          *
00563          * @access      private
00564          * @return      void
00565          */
00566         function _flashdata_mark()
00567         {
00568                 $userdata = $this->all_userdata();
00569                 foreach ($userdata as $name => $value)
00570                 {
00571                         $parts = explode(':new:', $name);
00572                         if (is_array($parts) && count($parts) === 2)
00573                         {
00574                                 $new_name = $this->flashdata_key.':old:'.$parts[1];
00575                                 $this->set_userdata($new_name, $value);
00576                                 $this->unset_userdata($name);
00577                         }
00578                 }
00579         }
00580 
00581         // ------------------------------------------------------------------------
00582 
00583         /**
00584          * Removes all flashdata marked as 'old'
00585          *
00586          * @access      private
00587          * @return      void
00588          */
00589 
00590         function _flashdata_sweep()
00591         {
00592                 $userdata = $this->all_userdata();
00593                 foreach ($userdata as $key => $value)
00594                 {
00595                         if (strpos($key, ':old:'))
00596                         {
00597                                 $this->unset_userdata($key);
00598                         }
00599                 }
00600 
00601         }
00602 
00603         // --------------------------------------------------------------------
00604         
00605         /**
00606          * Get the "now" time
00607          *
00608          * @access      private
00609          * @return      string
00610          */
00611         function _get_time()
00612         {
00613                 if (strtolower($this->time_reference) == 'gmt')
00614                 {
00615                         $now = time();
00616                         $time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
00617                 }
00618                 else
00619                 {
00620                         $time = time();
00621                 }
00622         
00623                 return $time;
00624         }
00625 
00626         // --------------------------------------------------------------------
00627         
00628         /**
00629          * Write the session cookie
00630          *
00631          * @access      public
00632          * @return      void
00633          */
00634         function _set_cookie($cookie_data = NULL)
00635         {
00636                 if (is_null($cookie_data))
00637                 {
00638                         $cookie_data = $this->userdata;
00639                 }
00640         
00641                 // Serialize the userdata for the cookie
00642                 $cookie_data = $this->_serialize($cookie_data);
00643                 
00644                 if ($this->sess_encrypt_cookie == TRUE)
00645                 {
00646                         $cookie_data = $this->CI->encrypt->encode($cookie_data);
00647                 }
00648                 else
00649                 {
00650                         // if encryption is not used, we provide an md5 hash to prevent userside tampering
00651                         $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
00652                 }
00653                 
00654                 // Set the cookie
00655                 setcookie(
00656                                         $this->sess_cookie_name,
00657                                         $cookie_data,
00658                                         $this->sess_expiration + time(),
00659                                         $this->cookie_path,
00660                                         $this->cookie_domain,
00661                                         0
00662                                 );
00663         }
00664 
00665         // --------------------------------------------------------------------
00666         
00667         /**
00668          * Serialize an array
00669          *
00670          * This function first converts any slashes found in the array to a temporary
00671          * marker, so when it gets unserialized the slashes will be preserved
00672          *
00673          * @access      private
00674          * @param       array
00675          * @return      string
00676          */     
00677         function _serialize($data)
00678         {
00679                 if (is_array($data))
00680                 {
00681                         foreach ($data as $key => $val)
00682                         {
00683                                 $data[$key] = str_replace('\\', '{{slash}}', $val);
00684                         }
00685                 }
00686                 else
00687                 {
00688                         $data = str_replace('\\', '{{slash}}', $data);
00689                 }
00690                 
00691                 return serialize($data);
00692         }
00693 
00694         // --------------------------------------------------------------------
00695         
00696         /**
00697          * Unserialize
00698          *
00699          * This function unserializes a data string, then converts any
00700          * temporary slash markers back to actual slashes
00701          *
00702          * @access      private
00703          * @param       array
00704          * @return      string
00705          */             
00706         function _unserialize($data)
00707         {
00708                 $data = @unserialize(strip_slashes($data));
00709                 
00710                 if (is_array($data))
00711                 {
00712                         foreach ($data as $key => $val)
00713                         {
00714                                 $data[$key] = str_replace('{{slash}}', '\\', $val);
00715                         }
00716                         
00717                         return $data;
00718                 }
00719                 
00720                 return str_replace('{{slash}}', '\\', $data);
00721         }
00722 
00723         // --------------------------------------------------------------------
00724         
00725         /**
00726          * Garbage collection
00727          *
00728          * This deletes expired session rows from database
00729          * if the probability percentage is met
00730          *
00731          * @access      public
00732          * @return      void
00733          */
00734         function _sess_gc()
00735         {
00736                 if ($this->sess_use_database != TRUE)
00737                 {
00738                         return;
00739                 }
00740                 
00741                 srand(time());
00742                 if ((rand() % 100) < $this->gc_probability)
00743                 {
00744                         $expire = $this->now - $this->sess_expiration;
00745                         
00746                         $this->CI->db->where("last_activity < {$expire}");
00747                         $this->CI->db->delete($this->sess_table_name);
00748 
00749                         log_message('debug', 'Session garbage collection performed.');
00750                 }
00751         }
00752 
00753         
00754 }
00755 // END Session Class
00756 
00757 /* End of file Session.php */
00758 /* Location: ./system/libraries/Session.php */