source: sourcecode/system/libraries/Session.php @ 1

Last change on this file since 1 was 1, checked in by dungnv, 11 years ago
File size: 18.8 KB
Line 
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 */
27class 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, 120)))
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, 120),
320                                                        'last_activity' => $this->now,
321                                                        'user_data'             => ''
322                                                        );
323
324
325                // Save the data to the DB if needed
326                if ($this->sess_use_database === TRUE)
327                {
328                        $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
329                }
330
331                // Write the cookie
332                $this->_set_cookie();
333        }
334
335        // --------------------------------------------------------------------
336
337        /**
338         * Update an existing session
339         *
340         * @access      public
341         * @return      void
342         */
343        function sess_update()
344        {
345                // We only update the session every five minutes by default
346                if (($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
347                {
348                        return;
349                }
350
351                // Save the old session id so we know which record to
352                // update in the database if we need it
353                $old_sessid = $this->userdata['session_id'];
354                $new_sessid = '';
355                while (strlen($new_sessid) < 32)
356                {
357                        $new_sessid .= mt_rand(0, mt_getrandmax());
358                }
359
360                // To make the session ID even more secure we'll combine it with the user's IP
361                $new_sessid .= $this->CI->input->ip_address();
362
363                // Turn it into a hash
364                $new_sessid = md5(uniqid($new_sessid, TRUE));
365
366                // Update the session data in the session data array
367                $this->userdata['session_id'] = $new_sessid;
368                $this->userdata['last_activity'] = $this->now;
369
370                // _set_cookie() will handle this for us if we aren't using database sessions
371                // by pushing all userdata to the cookie.
372                $cookie_data = NULL;
373
374                // Update the session ID and last_activity field in the DB if needed
375                if ($this->sess_use_database === TRUE)
376                {
377                        // set cookie explicitly to only have our session data
378                        $cookie_data = array();
379                        foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
380                        {
381                                $cookie_data[$val] = $this->userdata[$val];
382                        }
383
384                        $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)));
385                }
386
387                // Write the cookie
388                $this->_set_cookie($cookie_data);
389        }
390
391        // --------------------------------------------------------------------
392
393        /**
394         * Destroy the current session
395         *
396         * @access      public
397         * @return      void
398         */
399        function sess_destroy()
400        {
401                // Kill the session DB row
402                if ($this->sess_use_database === TRUE && isset($this->userdata['session_id']))
403                {
404                        $this->CI->db->where('session_id', $this->userdata['session_id']);
405                        $this->CI->db->delete($this->sess_table_name);
406                }
407
408                // Kill the cookie
409                setcookie(
410                                        $this->sess_cookie_name,
411                                        addslashes(serialize(array())),
412                                        ($this->now - 31500000),
413                                        $this->cookie_path,
414                                        $this->cookie_domain,
415                                        0
416                                );
417
418                // Kill session data
419                $this->userdata = array();
420        }
421
422        // --------------------------------------------------------------------
423
424        /**
425         * Fetch a specific item from the session array
426         *
427         * @access      public
428         * @param       string
429         * @return      string
430         */
431        function userdata($item)
432        {
433                return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
434        }
435
436        // --------------------------------------------------------------------
437
438        /**
439         * Fetch all session data
440         *
441         * @access      public
442         * @return      array
443         */
444        function all_userdata()
445        {
446                return $this->userdata;
447        }
448
449        // --------------------------------------------------------------------
450
451        /**
452         * Add or change data in the "userdata" array
453         *
454         * @access      public
455         * @param       mixed
456         * @param       string
457         * @return      void
458         */
459        function set_userdata($newdata = array(), $newval = '')
460        {
461                if (is_string($newdata))
462                {
463                        $newdata = array($newdata => $newval);
464                }
465
466                if (count($newdata) > 0)
467                {
468                        foreach ($newdata as $key => $val)
469                        {
470                                $this->userdata[$key] = $val;
471                        }
472                }
473
474                $this->sess_write();
475        }
476
477        // --------------------------------------------------------------------
478
479        /**
480         * Delete a session variable from the "userdata" array
481         *
482         * @access      array
483         * @return      void
484         */
485        function unset_userdata($newdata = array())
486        {
487                if (is_string($newdata))
488                {
489                        $newdata = array($newdata => '');
490                }
491
492                if (count($newdata) > 0)
493                {
494                        foreach ($newdata as $key => $val)
495                        {
496                                unset($this->userdata[$key]);
497                        }
498                }
499
500                $this->sess_write();
501        }
502
503        // ------------------------------------------------------------------------
504
505        /**
506         * Add or change flashdata, only available
507         * until the next request
508         *
509         * @access      public
510         * @param       mixed
511         * @param       string
512         * @return      void
513         */
514        function set_flashdata($newdata = array(), $newval = '')
515        {
516                if (is_string($newdata))
517                {
518                        $newdata = array($newdata => $newval);
519                }
520
521                if (count($newdata) > 0)
522                {
523                        foreach ($newdata as $key => $val)
524                        {
525                                $flashdata_key = $this->flashdata_key.':new:'.$key;
526                                $this->set_userdata($flashdata_key, $val);
527                        }
528                }
529        }
530
531        // ------------------------------------------------------------------------
532
533        /**
534         * Keeps existing flashdata available to next request.
535         *
536         * @access      public
537         * @param       string
538         * @return      void
539         */
540        function keep_flashdata($key)
541        {
542                // 'old' flashdata gets removed.  Here we mark all
543                // flashdata as 'new' to preserve it from _flashdata_sweep()
544                // Note the function will return FALSE if the $key
545                // provided cannot be found
546                $old_flashdata_key = $this->flashdata_key.':old:'.$key;
547                $value = $this->userdata($old_flashdata_key);
548
549                $new_flashdata_key = $this->flashdata_key.':new:'.$key;
550                $this->set_userdata($new_flashdata_key, $value);
551        }
552
553        // ------------------------------------------------------------------------
554
555        /**
556         * Fetch a specific flashdata item from the session array
557         *
558         * @access      public
559         * @param       string
560         * @return      string
561         */
562        function flashdata($key)
563        {
564                $flashdata_key = $this->flashdata_key.':old:'.$key;
565                return $this->userdata($flashdata_key);
566        }
567
568        // ------------------------------------------------------------------------
569
570        /**
571         * Identifies flashdata as 'old' for removal
572         * when _flashdata_sweep() runs.
573         *
574         * @access      private
575         * @return      void
576         */
577        function _flashdata_mark()
578        {
579                $userdata = $this->all_userdata();
580                foreach ($userdata as $name => $value)
581                {
582                        $parts = explode(':new:', $name);
583                        if (is_array($parts) && count($parts) === 2)
584                        {
585                                $new_name = $this->flashdata_key.':old:'.$parts[1];
586                                $this->set_userdata($new_name, $value);
587                                $this->unset_userdata($name);
588                        }
589                }
590        }
591
592        // ------------------------------------------------------------------------
593
594        /**
595         * Removes all flashdata marked as 'old'
596         *
597         * @access      private
598         * @return      void
599         */
600
601        function _flashdata_sweep()
602        {
603                $userdata = $this->all_userdata();
604                foreach ($userdata as $key => $value)
605                {
606                        if (strpos($key, ':old:'))
607                        {
608                                $this->unset_userdata($key);
609                        }
610                }
611
612        }
613
614        // --------------------------------------------------------------------
615
616        /**
617         * Get the "now" time
618         *
619         * @access      private
620         * @return      string
621         */
622        function _get_time()
623        {
624                if (strtolower($this->time_reference) == 'gmt')
625                {
626                        $now = time();
627                        $time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
628                }
629                else
630                {
631                        $time = time();
632                }
633
634                return $time;
635        }
636
637        // --------------------------------------------------------------------
638
639        /**
640         * Write the session cookie
641         *
642         * @access      public
643         * @return      void
644         */
645        function _set_cookie($cookie_data = NULL)
646        {
647                if (is_null($cookie_data))
648                {
649                        $cookie_data = $this->userdata;
650                }
651
652                // Serialize the userdata for the cookie
653                $cookie_data = $this->_serialize($cookie_data);
654
655                if ($this->sess_encrypt_cookie == TRUE)
656                {
657                        $cookie_data = $this->CI->encrypt->encode($cookie_data);
658                }
659                else
660                {
661                        // if encryption is not used, we provide an md5 hash to prevent userside tampering
662                        $cookie_data = $cookie_data.md5($cookie_data.$this->encryption_key);
663                }
664
665                $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
666
667                // Set the cookie
668                setcookie(
669                                        $this->sess_cookie_name,
670                                        $cookie_data,
671                                        $expire,
672                                        $this->cookie_path,
673                                        $this->cookie_domain,
674                                        $this->cookie_secure
675                                );
676        }
677
678        // --------------------------------------------------------------------
679
680        /**
681         * Serialize an array
682         *
683         * This function first converts any slashes found in the array to a temporary
684         * marker, so when it gets unserialized the slashes will be preserved
685         *
686         * @access      private
687         * @param       array
688         * @return      string
689         */
690        function _serialize($data)
691        {
692                if (is_array($data))
693                {
694                        foreach ($data as $key => $val)
695                        {
696                                if (is_string($val))
697                                {
698                                        $data[$key] = str_replace('\\', '{{slash}}', $val);
699                                }
700                        }
701                }
702                else
703                {
704                        if (is_string($data))
705                        {
706                                $data = str_replace('\\', '{{slash}}', $data);
707                        }
708                }
709
710                return serialize($data);
711        }
712
713        // --------------------------------------------------------------------
714
715        /**
716         * Unserialize
717         *
718         * This function unserializes a data string, then converts any
719         * temporary slash markers back to actual slashes
720         *
721         * @access      private
722         * @param       array
723         * @return      string
724         */
725        function _unserialize($data)
726        {
727                $data = @unserialize(strip_slashes($data));
728
729                if (is_array($data))
730                {
731                        foreach ($data as $key => $val)
732                        {
733                                if (is_string($val))
734                                {
735                                        $data[$key] = str_replace('{{slash}}', '\\', $val);
736                                }
737                        }
738
739                        return $data;
740                }
741
742                return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
743        }
744
745        // --------------------------------------------------------------------
746
747        /**
748         * Garbage collection
749         *
750         * This deletes expired session rows from database
751         * if the probability percentage is met
752         *
753         * @access      public
754         * @return      void
755         */
756        function _sess_gc()
757        {
758                if ($this->sess_use_database != TRUE)
759                {
760                        return;
761                }
762
763                srand(time());
764                if ((rand() % 100) < $this->gc_probability)
765                {
766                        $expire = $this->now - $this->sess_expiration;
767
768                        $this->CI->db->where("last_activity < {$expire}");
769                        $this->CI->db->delete($this->sess_table_name);
770
771                        log_message('debug', 'Session garbage collection performed.');
772                }
773        }
774
775
776}
777// END Session Class
778
779/* End of file Session.php */
780/* Location: ./system/libraries/Session.php */
Note: See TracBrowser for help on using the repository browser.