PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/bonfire/ci3/libraries/Session/drivers/Session_memcached_driver.php

http://github.com/ci-bonfire/Bonfire
PHP | 397 lines | 190 code | 50 blank | 157 comment | 26 complexity | 5560068d6f2424a86f40d13f395b8ccf MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP
  6. *
  7. * This content is released under the MIT License (MIT)
  8. *
  9. * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  27. * THE SOFTWARE.
  28. *
  29. * @package CodeIgniter
  30. * @author EllisLab Dev Team
  31. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
  32. * @copyright Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
  33. * @license http://opensource.org/licenses/MIT MIT License
  34. * @link https://codeigniter.com
  35. * @since Version 3.0.0
  36. * @filesource
  37. */
  38. defined('BASEPATH') OR exit('No direct script access allowed');
  39. /**
  40. * CodeIgniter Session Memcached Driver
  41. *
  42. * @package CodeIgniter
  43. * @subpackage Libraries
  44. * @category Sessions
  45. * @author Andrey Andreev
  46. * @link https://codeigniter.com/user_guide/libraries/sessions.html
  47. */
  48. class CI_Session_memcached_driver extends CI_Session_driver implements SessionHandlerInterface {
  49. /**
  50. * Memcached instance
  51. *
  52. * @var Memcached
  53. */
  54. protected $_memcached;
  55. /**
  56. * Key prefix
  57. *
  58. * @var string
  59. */
  60. protected $_key_prefix = 'ci_session:';
  61. /**
  62. * Lock key
  63. *
  64. * @var string
  65. */
  66. protected $_lock_key;
  67. // ------------------------------------------------------------------------
  68. /**
  69. * Class constructor
  70. *
  71. * @param array $params Configuration parameters
  72. * @return void
  73. */
  74. public function __construct(&$params)
  75. {
  76. parent::__construct($params);
  77. if (empty($this->_config['save_path']))
  78. {
  79. log_message('error', 'Session: No Memcached save path configured.');
  80. }
  81. if ($this->_config['match_ip'] === TRUE)
  82. {
  83. $this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':';
  84. }
  85. }
  86. // ------------------------------------------------------------------------
  87. /**
  88. * Open
  89. *
  90. * Sanitizes save_path and initializes connections.
  91. *
  92. * @param string $save_path Server path(s)
  93. * @param string $name Session cookie name, unused
  94. * @return bool
  95. */
  96. public function open($save_path, $name)
  97. {
  98. $this->_memcached = new Memcached();
  99. $this->_memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, TRUE); // required for touch() usage
  100. $server_list = array();
  101. foreach ($this->_memcached->getServerList() as $server)
  102. {
  103. $server_list[] = $server['host'].':'.$server['port'];
  104. }
  105. if ( ! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->_config['save_path'], $matches, PREG_SET_ORDER))
  106. {
  107. $this->_memcached = NULL;
  108. log_message('error', 'Session: Invalid Memcached save path format: '.$this->_config['save_path']);
  109. return $this->_fail();
  110. }
  111. foreach ($matches as $match)
  112. {
  113. // If Memcached already has this server (or if the port is invalid), skip it
  114. if (in_array($match[1].':'.$match[2], $server_list, TRUE))
  115. {
  116. log_message('debug', 'Session: Memcached server pool already has '.$match[1].':'.$match[2]);
  117. continue;
  118. }
  119. if ( ! $this->_memcached->addServer($match[1], $match[2], isset($match[3]) ? $match[3] : 0))
  120. {
  121. log_message('error', 'Could not add '.$match[1].':'.$match[2].' to Memcached server pool.');
  122. }
  123. else
  124. {
  125. $server_list[] = $match[1].':'.$match[2];
  126. }
  127. }
  128. if (empty($server_list))
  129. {
  130. log_message('error', 'Session: Memcached server pool is empty.');
  131. return $this->_fail();
  132. }
  133. $this->php5_validate_id();
  134. return $this->_success;
  135. }
  136. // ------------------------------------------------------------------------
  137. /**
  138. * Read
  139. *
  140. * Reads session data and acquires a lock
  141. *
  142. * @param string $session_id Session ID
  143. * @return string Serialized session data
  144. */
  145. public function read($session_id)
  146. {
  147. if (isset($this->_memcached) && $this->_get_lock($session_id))
  148. {
  149. // Needed by write() to detect session_regenerate_id() calls
  150. $this->_session_id = $session_id;
  151. $session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id);
  152. $this->_fingerprint = md5($session_data);
  153. return $session_data;
  154. }
  155. return $this->_fail();
  156. }
  157. // ------------------------------------------------------------------------
  158. /**
  159. * Write
  160. *
  161. * Writes (create / update) session data
  162. *
  163. * @param string $session_id Session ID
  164. * @param string $session_data Serialized session data
  165. * @return bool
  166. */
  167. public function write($session_id, $session_data)
  168. {
  169. if ( ! isset($this->_memcached, $this->_lock_key))
  170. {
  171. return $this->_fail();
  172. }
  173. // Was the ID regenerated?
  174. elseif ($session_id !== $this->_session_id)
  175. {
  176. if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
  177. {
  178. return $this->_fail();
  179. }
  180. $this->_fingerprint = md5('');
  181. $this->_session_id = $session_id;
  182. }
  183. $key = $this->_key_prefix.$session_id;
  184. $this->_memcached->replace($this->_lock_key, time(), 300);
  185. if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
  186. {
  187. if ($this->_memcached->set($key, $session_data, $this->_config['expiration']))
  188. {
  189. $this->_fingerprint = $fingerprint;
  190. return $this->_success;
  191. }
  192. return $this->_fail();
  193. }
  194. elseif (
  195. $this->_memcached->touch($key, $this->_config['expiration'])
  196. OR ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND && $this->_memcached->set($key, $session_data, $this->_config['expiration']))
  197. )
  198. {
  199. return $this->_success;
  200. }
  201. return $this->_fail();
  202. }
  203. // ------------------------------------------------------------------------
  204. /**
  205. * Close
  206. *
  207. * Releases locks and closes connection.
  208. *
  209. * @return bool
  210. */
  211. public function close()
  212. {
  213. if (isset($this->_memcached))
  214. {
  215. $this->_release_lock();
  216. if ( ! $this->_memcached->quit())
  217. {
  218. return $this->_fail();
  219. }
  220. $this->_memcached = NULL;
  221. return $this->_success;
  222. }
  223. return $this->_fail();
  224. }
  225. // ------------------------------------------------------------------------
  226. /**
  227. * Destroy
  228. *
  229. * Destroys the current session.
  230. *
  231. * @param string $session_id Session ID
  232. * @return bool
  233. */
  234. public function destroy($session_id)
  235. {
  236. if (isset($this->_memcached, $this->_lock_key))
  237. {
  238. $this->_memcached->delete($this->_key_prefix.$session_id);
  239. $this->_cookie_destroy();
  240. return $this->_success;
  241. }
  242. return $this->_fail();
  243. }
  244. // ------------------------------------------------------------------------
  245. /**
  246. * Garbage Collector
  247. *
  248. * Deletes expired sessions
  249. *
  250. * @param int $maxlifetime Maximum lifetime of sessions
  251. * @return bool
  252. */
  253. public function gc($maxlifetime)
  254. {
  255. // Not necessary, Memcached takes care of that.
  256. return $this->_success;
  257. }
  258. // --------------------------------------------------------------------
  259. /**
  260. * Validate ID
  261. *
  262. * Checks whether a session ID record exists server-side,
  263. * to enforce session.use_strict_mode.
  264. *
  265. * @param string $id
  266. * @return bool
  267. */
  268. public function validateId($id)
  269. {
  270. $this->_memcached->get($this->_key_prefix.$id);
  271. return ($this->_memcached->getResultCode() === Memcached::RES_SUCCESS);
  272. }
  273. // ------------------------------------------------------------------------
  274. /**
  275. * Get lock
  276. *
  277. * Acquires an (emulated) lock.
  278. *
  279. * @param string $session_id Session ID
  280. * @return bool
  281. */
  282. protected function _get_lock($session_id)
  283. {
  284. // PHP 7 reuses the SessionHandler object on regeneration,
  285. // so we need to check here if the lock key is for the
  286. // correct session ID.
  287. if ($this->_lock_key === $this->_key_prefix.$session_id.':lock')
  288. {
  289. if ( ! $this->_memcached->replace($this->_lock_key, time(), 300))
  290. {
  291. return ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND)
  292. ? $this->_memcached->add($this->_lock_key, time(), 300)
  293. : FALSE;
  294. }
  295. return TRUE;
  296. }
  297. // 30 attempts to obtain a lock, in case another request already has it
  298. $lock_key = $this->_key_prefix.$session_id.':lock';
  299. $attempt = 0;
  300. do
  301. {
  302. if ($this->_memcached->get($lock_key))
  303. {
  304. sleep(1);
  305. continue;
  306. }
  307. $method = ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND) ? 'add' : 'set';
  308. if ( ! $this->_memcached->$method($lock_key, time(), 300))
  309. {
  310. log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
  311. return FALSE;
  312. }
  313. $this->_lock_key = $lock_key;
  314. break;
  315. }
  316. while (++$attempt < 30);
  317. if ($attempt === 30)
  318. {
  319. log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.');
  320. return FALSE;
  321. }
  322. $this->_lock = TRUE;
  323. return TRUE;
  324. }
  325. // ------------------------------------------------------------------------
  326. /**
  327. * Release lock
  328. *
  329. * Releases a previously acquired lock
  330. *
  331. * @return bool
  332. */
  333. protected function _release_lock()
  334. {
  335. if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
  336. {
  337. if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
  338. {
  339. log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key);
  340. return FALSE;
  341. }
  342. $this->_lock_key = NULL;
  343. $this->_lock = FALSE;
  344. }
  345. return TRUE;
  346. }
  347. }