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