/halogy/libraries/Session.php

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