PageRenderTime 70ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 1ms

/system/codeigniter/system/libraries/Session.php

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