Remove file execution permission.
[living-lab-site.git] / system / libraries / Session.php
1 <?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2 /**
3  * CodeIgniter
4  *
5  * An open source application development framework for PHP 5.1.6 or newer
6  *
7  * @package             CodeIgniter
8  * @author              ExpressionEngine Dev Team
9  * @copyright   Copyright (c) 2008 - 2011, EllisLab, Inc.
10  * @license             http://codeigniter.com/user_guide/license.html
11  * @link                http://codeigniter.com
12  * @since               Version 1.0
13  * @filesource
14  */
15
16 // ------------------------------------------------------------------------
17
18 /**
19  * Session Class
20  *
21  * @package             CodeIgniter
22  * @subpackage  Libraries
23  * @category    Sessions
24  * @author              ExpressionEngine Dev Team
25  * @link                http://codeigniter.com/user_guide/libraries/sessions.html
26  */
27 class CI_Session {
28
29         var $sess_encrypt_cookie                = FALSE;
30         var $sess_use_database                  = FALSE;
31         var $sess_table_name                    = '';
32         var $sess_expiration                    = 7200;
33         var $sess_expire_on_close               = FALSE;
34         var $sess_match_ip                              = FALSE;
35         var $sess_match_useragent               = TRUE;
36         var $sess_cookie_name                   = 'ci_session';
37         var $cookie_prefix                              = '';
38         var $cookie_path                                = '';
39         var $cookie_domain                              = '';
40         var $cookie_secure                              = FALSE;
41         var $sess_time_to_update                = 300;
42         var $encryption_key                             = '';
43         var $flashdata_key                              = 'flash';
44         var $time_reference                             = 'time';
45         var $gc_probability                             = 5;
46         var $userdata                                   = array();
47         var $CI;
48         var $now;
49
50         /**
51          * Session Constructor
52          *
53          * The constructor runs the session routines automatically
54          * whenever the class is instantiated.
55          */
56         public function __construct($params = array())
57         {
58                 log_message('debug', "Session Class Initialized");
59
60                 // Set the super object to a local variable for use throughout the class
61                 $this->CI =& get_instance();
62
63                 // Set all the session preferences, which can either be set
64                 // manually via the $params array above or via the config file
65                 foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
66                 {
67                         $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
68                 }
69
70                 if ($this->encryption_key == '')
71                 {
72                         show_error('In order to use the Session class you are required to set an encryption key in your config file.');
73                 }
74
75                 // Load the string helper so we can use the strip_slashes() function
76                 $this->CI->load->helper('string');
77
78                 // Do we need encryption? If so, load the encryption class
79                 if ($this->sess_encrypt_cookie == TRUE)
80                 {
81                         $this->CI->load->library('encrypt');
82                 }
83
84                 // Are we using a database?  If so, load it
85                 if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')
86                 {
87                         $this->CI->load->database();
88                 }
89
90                 // Set the "now" time.  Can either be GMT or server time, based on the
91                 // config prefs.  We use this to set the "last activity" time
92                 $this->now = $this->_get_time();
93
94                 // Set the session length. If the session expiration is
95                 // set to zero we'll set the expiration two years from now.
96                 if ($this->sess_expiration == 0)
97                 {
98                         $this->sess_expiration = (60*60*24*365*2);
99                 }
100                 
101                 // Set the cookie name
102                 $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
103
104                 // Run the Session routine. If a session doesn't exist we'll
105                 // create a new one.  If it does, we'll update it.
106                 if ( ! $this->sess_read())
107                 {
108                         $this->sess_create();
109                 }
110                 else
111                 {
112                         $this->sess_update();
113                 }
114
115                 // Delete 'old' flashdata (from last request)
116                 $this->_flashdata_sweep();
117
118                 // Mark all new flashdata as old (data will be deleted before next request)
119                 $this->_flashdata_mark();
120
121                 // Delete expired sessions if necessary
122                 $this->_sess_gc();
123
124                 log_message('debug', "Session routines successfully run");
125         }
126
127         // --------------------------------------------------------------------
128
129         /**
130          * Fetch the current session data if it exists
131          *
132          * @access      public
133          * @return      bool
134          */
135         function sess_read()
136         {
137                 // Fetch the cookie
138                 $session = $this->CI->input->cookie($this->sess_cookie_name);
139
140                 // No cookie?  Goodbye cruel world!...
141                 if ($session === FALSE)
142                 {
143                         log_message('debug', 'A session cookie was not found.');
144                         return FALSE;
145                 }
146
147                 // Decrypt the cookie data
148                 if ($this->sess_encrypt_cookie == TRUE)
149                 {
150                         $session = $this->CI->encrypt->decode($session);
151                 }
152                 else
153                 {
154                         // encryption was not used, so we need to check the md5 hash
155                         $hash    = substr($session, strlen($session)-32); // get last 32 chars
156                         $session = substr($session, 0, strlen($session)-32);
157
158                         // Does the md5 hash match?  This is to prevent manipulation of session data in userspace
159                         if ($hash !==  md5($session.$this->encryption_key))
160                         {
161                                 log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.');
162                                 $this->sess_destroy();
163                                 return FALSE;
164                         }
165                 }
166
167                 // Unserialize the session array
168                 $session = $this->_unserialize($session);
169
170                 // Is the session data we unserialized an array with the correct format?
171                 if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))
172                 {
173                         $this->sess_destroy();
174                         return FALSE;
175                 }
176
177                 // Is the session current?
178                 if (($session['last_activity'] + $this->sess_expiration) < $this->now)
179                 {
180                         $this->sess_destroy();
181                         return FALSE;
182                 }
183
184                 // Does the IP Match?
185                 if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
186                 {
187                         $this->sess_destroy();
188                         return FALSE;
189                 }
190
191                 // Does the User Agent Match?
192                 if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 50)))
193                 {
194                         $this->sess_destroy();
195                         return FALSE;
196                 }
197
198                 // Is there a corresponding session in the DB?
199                 if ($this->sess_use_database === TRUE)
200                 {
201                         $this->CI->db->where('session_id', $session['session_id']);
202
203                         if ($this->sess_match_ip == TRUE)
204                         {
205                                 $this->CI->db->where('ip_address', $session['ip_address']);
206                         }
207
208                         if ($this->sess_match_useragent == TRUE)
209                         {
210                                 $this->CI->db->where('user_agent', $session['user_agent']);
211                         }
212
213                         $query = $this->CI->db->get($this->sess_table_name);
214
215                         // No result?  Kill it!
216                         if ($query->num_rows() == 0)
217                         {
218                                 $this->sess_destroy();
219                                 return FALSE;
220                         }
221
222                         // Is there custom data?  If so, add it to the main session array
223                         $row = $query->row();
224                         if (isset($row->user_data) AND $row->user_data != '')
225                         {
226                                 $custom_data = $this->_unserialize($row->user_data);
227
228                                 if (is_array($custom_data))
229                                 {
230                                         foreach ($custom_data as $key => $val)
231                                         {
232                                                 $session[$key] = $val;
233                                         }
234                                 }
235                         }
236                 }
237
238                 // Session is valid!
239                 $this->userdata = $session;
240                 unset($session);
241
242                 return TRUE;
243         }
244
245         // --------------------------------------------------------------------
246
247         /**
248          * Write the session data
249          *
250          * @access      public
251          * @return      void
252          */
253         function sess_write()
254         {
255                 // Are we saving custom data to the DB?  If not, all we do is update the cookie
256                 if ($this->sess_use_database === FALSE)
257                 {
258                         $this->_set_cookie();
259                         return;
260                 }
261
262                 // set the custom userdata, the session data we will set in a second
263                 $custom_userdata = $this->userdata;
264                 $cookie_userdata = array();
265
266                 // Before continuing, we need to determine if there is any custom data to deal with.
267                 // Let's determine this by removing the default indexes to see if there's anything left in the array
268                 // and set the session data while we're at it
269                 foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
270                 {
271                         unset($custom_userdata[$val]);
272                         $cookie_userdata[$val] = $this->userdata[$val];
273                 }
274
275                 // Did we find any custom data?  If not, we turn the empty array into a string
276                 // since there's no reason to serialize and store an empty array in the DB
277                 if (count($custom_userdata) === 0)
278                 {
279                         $custom_userdata = '';
280                 }
281                 else
282                 {
283                         // Serialize the custom data array so we can store it
284                         $custom_userdata = $this->_serialize($custom_userdata);
285                 }
286
287                 // Run the update query
288                 $this->CI->db->where('session_id', $this->userdata['session_id']);
289                 $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
290
291                 // Write the cookie.  Notice that we manually pass the cookie data array to the
292                 // _set_cookie() function. Normally that function will store $this->userdata, but
293                 // in this case that array contains custom data, which we do not want in the cookie.
294                 $this->_set_cookie($cookie_userdata);
295         }
296
297         // --------------------------------------------------------------------
298
299         /**
300          * Create a new session
301          *
302          * @access      public
303          * @return      void
304          */
305         function sess_create()
306         {
307                 $sessid = '';
308                 while (strlen($sessid) < 32)
309                 {
310                         $sessid .= mt_rand(0, mt_getrandmax());
311                 }
312
313                 // To make the session ID even more secure we'll combine it with the user's IP
314                 $sessid .= $this->CI->input->ip_address();
315
316                 $this->userdata = array(
317                                                         'session_id'    => md5(uniqid($sessid, TRUE)),
318                                                         'ip_address'    => $this->CI->input->ip_address(),
319                                                         'user_agent'    => substr($this->CI->input->user_agent(), 0, 50),
320                                                         'last_activity' => $this->now
321                                                         );
322
323
324                 // Save the data to the DB if needed
325                 if ($this->sess_use_database === TRUE)
326                 {
327                         $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
328                 }
329
330                 // Write the cookie
331                 $this->_set_cookie();
332         }
333
334         // --------------------------------------------------------------------
335
336         /**
337          * Update an existing session
338          *
339          * @access      public
340          * @return      void
341          */
342         function sess_update()
343         {
344                 // We only update the session every five minutes by default
345                 if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
346                 {
347                         return;
348                 }
349
350                 // Save the old session id so we know which record to
351                 // update in the database if we need it
352                 $old_sessid = $this->userdata['session_id'];
353                 $new_sessid = '';
354                 while (strlen($new_sessid) < 32)
355                 {
356                         $new_sessid .= mt_rand(0, mt_getrandmax());
357                 }
358
359                 // To make the session ID even more secure we'll combine it with the user's IP
360                 $new_sessid .= $this->CI->input->ip_address();
361
362                 // Turn it into a hash
363                 $new_sessid = md5(uniqid($new_sessid, TRUE));
364
365                 // Update the session data in the session data array
366                 $this->userdata['session_id'] = $new_sessid;
367                 $this->userdata['last_activity'] = $this->now;
368
369                 // _set_cookie() will handle this for us if we aren't using database sessions
370                 // by pushing all userdata to the cookie.
371                 $cookie_data = NULL;
372
373                 // Update the session ID and last_activity field in the DB if needed
374                 if ($this->sess_use_database === TRUE)
375                 {
376                         // set cookie explicitly to only have our session data
377                         $cookie_data = array();
378                         foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
379                         {
380                                 $cookie_data[$val] = $this->userdata[$val];
381                         }
382
383                         $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)));
384                 }
385
386                 // Write the cookie
387                 $this->_set_cookie($cookie_data);
388         }
389
390         // --------------------------------------------------------------------
391
392         /**
393          * Destroy the current session
394          *
395          * @access      public
396          * @return      void
397          */
398         function sess_destroy()
399         {
400                 // Kill the session DB row
401                 if ($this->sess_use_database === TRUE AND isset($this->userdata['session_id']))
402                 {
403                         $this->CI->db->where('session_id', $this->userdata['session_id']);
404                         $this->CI->db->delete($this->sess_table_name);
405                 }
406
407                 // Kill the cookie
408                 setcookie(
409                                         $this->sess_cookie_name,
410                                         addslashes(serialize(array())),
411                                         ($this->now - 31500000),
412                                         $this->cookie_path,
413                                         $this->cookie_domain,
414                                         0
415                                 );
416         }
417
418         // --------------------------------------------------------------------
419
420         /**
421          * Fetch a specific item from the session array
422          *
423          * @access      public
424          * @param       string
425          * @return      string
426          */
427         function userdata($item)
428         {
429                 return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
430         }
431
432         // --------------------------------------------------------------------
433
434         /**
435          * Fetch all session data
436          *
437          * @access      public
438          * @return      mixed
439          */
440         function all_userdata()
441         {
442                 return ( ! isset($this->userdata)) ? FALSE : $this->userdata;
443         }
444
445         // --------------------------------------------------------------------
446
447         /**
448          * Add or change data in the "userdata" array
449          *
450          * @access      public
451          * @param       mixed
452          * @param       string
453          * @return      void
454          */
455         function set_userdata($newdata = array(), $newval = '')
456         {
457                 if (is_string($newdata))
458                 {
459                         $newdata = array($newdata => $newval);
460                 }
461
462                 if (count($newdata) > 0)
463                 {
464                         foreach ($newdata as $key => $val)
465                         {
466                                 $this->userdata[$key] = $val;
467                         }
468                 }
469
470                 $this->sess_write();
471         }
472
473         // --------------------------------------------------------------------
474
475         /**
476          * Delete a session variable from the "userdata" array
477          *
478          * @access      array
479          * @return      void
480          */
481         function unset_userdata($newdata = array())
482         {
483                 if (is_string($newdata))
484                 {
485                         $newdata = array($newdata => '');
486                 }
487
488                 if (count($newdata) > 0)
489                 {
490                         foreach ($newdata as $key => $val)
491                         {
492                                 unset($this->userdata[$key]);
493                         }
494                 }
495
496                 $this->sess_write();
497         }
498
499         // ------------------------------------------------------------------------
500
501         /**
502          * Add or change flashdata, only available
503          * until the next request
504          *
505          * @access      public
506          * @param       mixed
507          * @param       string
508          * @return      void
509          */
510         function set_flashdata($newdata = array(), $newval = '')
511         {
512                 if (is_string($newdata))
513                 {
514                         $newdata = array($newdata => $newval);
515                 }
516
517                 if (count($newdata) > 0)
518                 {
519                         foreach ($newdata as $key => $val)
520                         {
521                                 $flashdata_key = $this->flashdata_key.':new:'.$key;
522                                 $this->set_userdata($flashdata_key, $val);
523                         }
524                 }
525         }
526
527         // ------------------------------------------------------------------------
528
529         /**
530          * Keeps existing flashdata available to next request.
531          *
532          * @access      public
533          * @param       string
534          * @return      void
535          */
536         function keep_flashdata($key)
537         {
538                 // 'old' flashdata gets removed.  Here we mark all
539                 // flashdata as 'new' to preserve it from _flashdata_sweep()
540                 // Note the function will return FALSE if the $key
541                 // provided cannot be found
542                 $old_flashdata_key = $this->flashdata_key.':old:'.$key;
543                 $value = $this->userdata($old_flashdata_key);
544
545                 $new_flashdata_key = $this->flashdata_key.':new:'.$key;
546                 $this->set_userdata($new_flashdata_key, $value);
547         }
548
549         // ------------------------------------------------------------------------
550
551         /**
552          * Fetch a specific flashdata item from the session array
553          *
554          * @access      public
555          * @param       string
556          * @return      string
557          */
558         function flashdata($key)
559         {
560                 $flashdata_key = $this->flashdata_key.':old:'.$key;
561                 return $this->userdata($flashdata_key);
562         }
563
564         // ------------------------------------------------------------------------
565
566         /**
567          * Identifies flashdata as 'old' for removal
568          * when _flashdata_sweep() runs.
569          *
570          * @access      private
571          * @return      void
572          */
573         function _flashdata_mark()
574         {
575                 $userdata = $this->all_userdata();
576                 foreach ($userdata as $name => $value)
577                 {
578                         $parts = explode(':new:', $name);
579                         if (is_array($parts) && count($parts) === 2)
580                         {
581                                 $new_name = $this->flashdata_key.':old:'.$parts[1];
582                                 $this->set_userdata($new_name, $value);
583                                 $this->unset_userdata($name);
584                         }
585                 }
586         }
587
588         // ------------------------------------------------------------------------
589
590         /**
591          * Removes all flashdata marked as 'old'
592          *
593          * @access      private
594          * @return      void
595          */
596
597         function _flashdata_sweep()
598         {
599                 $userdata = $this->all_userdata();
600                 foreach ($userdata as $key => $value)
601                 {
602                         if (strpos($key, ':old:'))
603                         {
604                                 $this->unset_userdata($key);
605                         }
606                 }
607
608         }
609
610         // --------------------------------------------------------------------
611
612         /**
613          * Get the "now" time
614          *
615          * @access      private
616          * @return      string
617          */
618         function _get_time()
619         {
620                 if (strtolower($this->time_reference) == 'gmt')
621                 {
622                         $now = time();
623                         $time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
624                 }
625                 else
626                 {
627                         $time = time();
628                 }
629
630                 return $time;
631         }
632
633         // --------------------------------------------------------------------
634
635         /**
636          * Write the session cookie
637          *
638          * @access      public
639          * @return      void
640          */
641         function _set_cookie($cookie_data = NULL)
642         {
643                 if (is_null($cookie_data))
644                 {
645                         $cookie_data = $this->userdata;
646                 }
647
648                 // Serialize the userdata for the cookie
649                 $cookie_data = $this->_serialize($cookie_data);
650
651                 if ($this->sess_encrypt_cookie == TRUE)
652                 {
653                         $cookie_data = $this->CI->encrypt->encode($cookie_data);
654                 }
655                 else
656                 {
657                         // if encryption is not used, we provide an md5 hash to prevent userside tampering
658                         $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
659                 }
660
661                 $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
662
663                 // Set the cookie
664                 setcookie(
665                                         $this->sess_cookie_name,
666                                         $cookie_data,
667                                         $expire,
668                                         $this->cookie_path,
669                                         $this->cookie_domain,
670                                         $this->cookie_secure
671                                 );
672         }
673
674         // --------------------------------------------------------------------
675
676         /**
677          * Serialize an array
678          *
679          * This function first converts any slashes found in the array to a temporary
680          * marker, so when it gets unserialized the slashes will be preserved
681          *
682          * @access      private
683          * @param       array
684          * @return      string
685          */
686         function _serialize($data)
687         {
688                 if (is_array($data))
689                 {
690                         foreach ($data as $key => $val)
691                         {
692                                 if (is_string($val))
693                                 {
694                                         $data[$key] = str_replace('\\', '{{slash}}', $val);
695                                 }
696                         }
697                 }
698                 else
699                 {
700                         if (is_string($data))
701                         {
702                                 $data = str_replace('\\', '{{slash}}', $data);
703                         }
704                 }
705
706                 return serialize($data);
707         }
708
709         // --------------------------------------------------------------------
710
711         /**
712          * Unserialize
713          *
714          * This function unserializes a data string, then converts any
715          * temporary slash markers back to actual slashes
716          *
717          * @access      private
718          * @param       array
719          * @return      string
720          */
721         function _unserialize($data)
722         {
723                 $data = @unserialize(strip_slashes($data));
724
725                 if (is_array($data))
726                 {
727                         foreach ($data as $key => $val)
728                         {
729                                 if (is_string($val))
730                                 {
731                                         $data[$key] = str_replace('{{slash}}', '\\', $val);
732                                 }
733                         }
734
735                         return $data;
736                 }
737
738                 return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
739         }
740
741         // --------------------------------------------------------------------
742
743         /**
744          * Garbage collection
745          *
746          * This deletes expired session rows from database
747          * if the probability percentage is met
748          *
749          * @access      public
750          * @return      void
751          */
752         function _sess_gc()
753         {
754                 if ($this->sess_use_database != TRUE)
755                 {
756                         return;
757                 }
758
759                 srand(time());
760                 if ((rand() % 100) < $this->gc_probability)
761                 {
762                         $expire = $this->now - $this->sess_expiration;
763
764                         $this->CI->db->where("last_activity < {$expire}");
765                         $this->CI->db->delete($this->sess_table_name);
766
767                         log_message('debug', 'Session garbage collection performed.');
768                 }
769         }
770
771
772 }
773 // END Session Class
774
775 /* End of file Session.php */
776 /* Location: ./system/libraries/Session.php */