file.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. // --------------------------------------------------------------------
  14. class Session_File extends \Session_Driver
  15. {
  16. /**
  17. * array of driver config defaults
  18. */
  19. protected static $_defaults = array(
  20. 'cookie_name' => 'fuelfid', // name of the session cookie for file based sessions
  21. 'path' => '/tmp', // path where the session files should be stored
  22. 'gc_probability' => 5 // probability % (between 0 and 100) for garbage collection
  23. );
  24. // --------------------------------------------------------------------
  25. public function __construct($config = array())
  26. {
  27. // merge the driver config with the global config
  28. $this->config = array_merge($config, is_array($config['file']) ? $config['file'] : static::$_defaults);
  29. $this->config = $this->_validate_config($this->config);
  30. }
  31. // --------------------------------------------------------------------
  32. /**
  33. * create a new session
  34. *
  35. * @access public
  36. * @return Fuel\Core\Session_File
  37. */
  38. public function create()
  39. {
  40. // create a new session
  41. $this->keys['session_id'] = $this->_new_session_id();
  42. $this->keys['previous_id'] = $this->keys['session_id']; // prevents errors if previous_id has a unique index
  43. $this->keys['ip_hash'] = md5(\Input::ip().\Input::real_ip());
  44. $this->keys['user_agent'] = \Input::user_agent();
  45. $this->keys['created'] = $this->time->get_timestamp();
  46. $this->keys['updated'] = $this->keys['created'];
  47. return $this;
  48. }
  49. // --------------------------------------------------------------------
  50. /**
  51. * read the session
  52. *
  53. * @access public
  54. * @param boolean, set to true if we want to force a new session to be created
  55. * @return Fuel\Core\Session_Driver
  56. */
  57. public function read($force = false)
  58. {
  59. // initialize the session
  60. $this->data = array();
  61. $this->keys = array();
  62. $this->flash = array();
  63. // get the session cookie
  64. $cookie = $this->_get_cookie();
  65. // if a cookie was present, find the session record
  66. if ($cookie and ! $force and isset($cookie[0]))
  67. {
  68. // read the session file
  69. $payload = $this->_read_file($cookie[0]);
  70. if ($payload === false)
  71. {
  72. // cookie present, but session record missing. force creation of a new session
  73. return $this->read(true);
  74. }
  75. // unpack the payload
  76. $payload = $this->_unserialize($payload);
  77. // session referral?
  78. if (isset($payload['rotated_session_id']))
  79. {
  80. $payload = $this->_read_file($payload['rotated_session_id']);
  81. if ($payload === false)
  82. {
  83. // cookie present, but session record missing. force creation of a new session
  84. return $this->read(true);
  85. }
  86. else
  87. {
  88. // unpack the payload
  89. $payload = $this->_unserialize($payload);
  90. }
  91. }
  92. if ( ! isset($payload[0]) or ! is_array($payload[0]))
  93. {
  94. // not a valid cookie payload
  95. }
  96. elseif ($payload[0]['updated'] + $this->config['expiration_time'] <= $this->time->get_timestamp())
  97. {
  98. // session has expired
  99. }
  100. elseif ($this->config['match_ip'] and $payload[0]['ip_hash'] !== md5(\Input::ip().\Input::real_ip()))
  101. {
  102. // IP address doesn't match
  103. }
  104. elseif ($this->config['match_ua'] and $payload[0]['user_agent'] !== \Input::user_agent())
  105. {
  106. // user agent doesn't match
  107. }
  108. else
  109. {
  110. // session is valid, retrieve the payload
  111. if (isset($payload[0]) and is_array($payload[0])) $this->keys = $payload[0];
  112. if (isset($payload[1]) and is_array($payload[1])) $this->data = $payload[1];
  113. if (isset($payload[2]) and is_array($payload[2])) $this->flash = $payload[2];
  114. }
  115. }
  116. return parent::read();
  117. }
  118. // --------------------------------------------------------------------
  119. /**
  120. * write the session
  121. *
  122. * @access public
  123. * @return Fuel\Core\Session_File
  124. */
  125. public function write()
  126. {
  127. // do we have something to write?
  128. if ( ! empty($this->keys) or ! empty($this->data) or ! empty($this->flash))
  129. {
  130. parent::write();
  131. // rotate the session id if needed
  132. $this->rotate(false);
  133. // session payload
  134. $payload = $this->_serialize(array($this->keys, $this->data, $this->flash));
  135. // create the session file
  136. $this->_write_file($this->keys['session_id'], $payload);
  137. // was the session id rotated?
  138. if ( isset($this->keys['previous_id']) and $this->keys['previous_id'] != $this->keys['session_id'])
  139. {
  140. // point the old session file to the new one, we don't want to lose the session
  141. $payload = $this->_serialize(array('rotated_session_id' => $this->keys['session_id']));
  142. $this->_write_file($this->keys['previous_id'], $payload);
  143. }
  144. // then update the cookie
  145. $this->_set_cookie(array($this->keys['session_id']));
  146. // do some garbage collection
  147. if (mt_rand(0,100) < $this->config['gc_probability'])
  148. {
  149. if ($handle = opendir($this->config['path']))
  150. {
  151. $expire = $this->time->get_timestamp() - $this->config['expiration_time'];
  152. while (($file = readdir($handle)) !== false)
  153. {
  154. if (filetype($this->config['path'] . $file) == 'file' and
  155. strpos($file, $this->config['cookie_name'].'_') === 0 and
  156. filemtime($this->config['path'] . $file) < $expire)
  157. {
  158. @unlink($this->config['path'] . $file);
  159. }
  160. }
  161. closedir($handle);
  162. }
  163. }
  164. }
  165. return $this;
  166. }
  167. // --------------------------------------------------------------------
  168. /**
  169. * destroy the current session
  170. *
  171. * @access public
  172. * @return Fuel\Core\Session_File
  173. */
  174. public function destroy()
  175. {
  176. // do we have something to destroy?
  177. if ( ! empty($this->keys))
  178. {
  179. // delete the session file
  180. $file = $this->config['path'].$this->config['cookie_name'].'_'.$this->keys['session_id'];
  181. if (file_exists($file))
  182. {
  183. unlink($file);
  184. }
  185. }
  186. parent::destroy();
  187. return $this;
  188. }
  189. // --------------------------------------------------------------------
  190. /**
  191. * Writes the session file
  192. *
  193. * @access private
  194. * @return boolean, true if it was an existing session, false if not
  195. */
  196. protected function _write_file($session_id, $payload)
  197. {
  198. // create the session file
  199. $file = $this->config['path'].$this->config['cookie_name'].'_'.$session_id;
  200. $exists = file_exists($file);
  201. $handle = fopen($file,'c');
  202. if ($handle)
  203. {
  204. // wait for a lock
  205. while(!flock($handle, LOCK_EX));
  206. // erase existing contents
  207. ftruncate($handle, 0);
  208. // write the session data
  209. fwrite($handle, $payload);
  210. //release the lock
  211. flock($handle, LOCK_UN);
  212. // close the file
  213. fclose($handle);
  214. }
  215. return $exists;
  216. }
  217. // --------------------------------------------------------------------
  218. /**
  219. * Reads the session file
  220. *
  221. * @access private
  222. * @return mixed, the payload if the file exists, or false if not
  223. */
  224. protected function _read_file($session_id)
  225. {
  226. $payload = false;
  227. $file = $this->config['path'].$this->config['cookie_name'].'_'.$session_id;
  228. if (file_exists($file))
  229. {
  230. $handle = fopen($file,'r');
  231. if ($handle)
  232. {
  233. // wait for a lock
  234. while(!flock($handle, LOCK_SH));
  235. // read the session data
  236. $payload = fread($handle, filesize($file));
  237. //release the lock
  238. flock($handle, LOCK_UN);
  239. // close the file
  240. fclose($handle);
  241. }
  242. }
  243. return $payload;
  244. }
  245. // --------------------------------------------------------------------
  246. /**
  247. * validate a driver config value
  248. *
  249. * @param array array with configuration values
  250. * @access public
  251. * @return array validated and consolidated config
  252. */
  253. public function _validate_config($config)
  254. {
  255. $validated = array();
  256. foreach ($config as $name => $item)
  257. {
  258. // filter out any driver config
  259. if (!is_array($item))
  260. {
  261. switch ($name)
  262. {
  263. case 'cookie_name':
  264. if ( empty($item) OR ! is_string($item))
  265. {
  266. $item = 'fuelfid';
  267. }
  268. break;
  269. case 'path':
  270. // do we have a path?
  271. if ( empty($item) OR ! is_dir($item))
  272. {
  273. throw new \FuelException('You have specify a valid path to store the session data files.');
  274. }
  275. // and can we write to it?
  276. if ( ! is_writable($item))
  277. {
  278. throw new \FuelException('The webserver doesn\'t have write access to the path to store the session data files.');
  279. }
  280. // update the path, and add the trailing slash
  281. $item = realpath($item).'/';
  282. break;
  283. case 'gc_probability':
  284. // do we have a path?
  285. if ( ! is_numeric($item) OR $item < 0 OR $item > 100)
  286. {
  287. // default value: 5%
  288. $item = 5;
  289. }
  290. break;
  291. default:
  292. // no config item for this driver
  293. break;
  294. }
  295. // global config, was validated in the driver
  296. $validated[$name] = $item;
  297. }
  298. }
  299. // validate all global settings as well
  300. return parent::_validate_config($validated);
  301. }
  302. }