Subversion-Projekte lars-tiefland.ci

Revision

Revision 2242 | Revision 2257 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
68 lars 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
 *
2254 lars 9
 * Copyright (c) 2014 - 2017, British Columbia Institute of Technology
68 lars 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/)
2254 lars 32
 * @copyright	Copyright (c) 2014 - 2017, British Columbia Institute of Technology (http://bcit.ca/)
68 lars 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
/**
41
 * CodeIgniter Session Memcached Driver
42
 *
43
 * @package	CodeIgniter
44
 * @subpackage	Libraries
45
 * @category	Sessions
46
 * @author	Andrey Andreev
47
 * @link	https://codeigniter.com/user_guide/libraries/sessions.html
48
 */
49
class CI_Session_memcached_driver extends CI_Session_driver implements SessionHandlerInterface {
50
 
51
	/**
52
	 * Memcached instance
53
	 *
54
	 * @var	Memcached
55
	 */
56
	protected $_memcached;
57
 
58
	/**
59
	 * Key prefix
60
	 *
61
	 * @var	string
62
	 */
63
	protected $_key_prefix = 'ci_session:';
64
 
65
	/**
66
	 * Lock key
67
	 *
68
	 * @var	string
69
	 */
70
	protected $_lock_key;
71
 
72
	// ------------------------------------------------------------------------
73
 
74
	/**
75
	 * Class constructor
76
	 *
77
	 * @param	array	$params	Configuration parameters
78
	 * @return	void
79
	 */
80
	public function __construct(&$params)
81
	{
82
		parent::__construct($params);
83
 
84
		if (empty($this->_config['save_path']))
85
		{
86
			log_message('error', 'Session: No Memcached save path configured.');
87
		}
88
 
89
		if ($this->_config['match_ip'] === TRUE)
90
		{
91
			$this->_key_prefix .= $_SERVER['REMOTE_ADDR'].':';
92
		}
93
	}
94
 
95
	// ------------------------------------------------------------------------
96
 
97
	/**
98
	 * Open
99
	 *
100
	 * Sanitizes save_path and initializes connections.
101
	 *
102
	 * @param	string	$save_path	Server path(s)
103
	 * @param	string	$name		Session cookie name, unused
104
	 * @return	bool
105
	 */
106
	public function open($save_path, $name)
107
	{
108
		$this->_memcached = new Memcached();
109
		$this->_memcached->setOption(Memcached::OPT_BINARY_PROTOCOL, TRUE); // required for touch() usage
110
		$server_list = array();
111
		foreach ($this->_memcached->getServerList() as $server)
112
		{
113
			$server_list[] = $server['host'].':'.$server['port'];
114
		}
115
 
116
		if ( ! preg_match_all('#,?([^,:]+)\:(\d{1,5})(?:\:(\d+))?#', $this->_config['save_path'], $matches, PREG_SET_ORDER))
117
		{
118
			$this->_memcached = NULL;
119
			log_message('error', 'Session: Invalid Memcached save path format: '.$this->_config['save_path']);
120
			return $this->_fail();
121
		}
122
 
123
		foreach ($matches as $match)
124
		{
125
			// If Memcached already has this server (or if the port is invalid), skip it
126
			if (in_array($match[1].':'.$match[2], $server_list, TRUE))
127
			{
128
				log_message('debug', 'Session: Memcached server pool already has '.$match[1].':'.$match[2]);
129
				continue;
130
			}
131
 
132
			if ( ! $this->_memcached->addServer($match[1], $match[2], isset($match[3]) ? $match[3] : 0))
133
			{
134
				log_message('error', 'Could not add '.$match[1].':'.$match[2].' to Memcached server pool.');
135
			}
136
			else
137
			{
138
				$server_list[] = $match[1].':'.$match[2];
139
			}
140
		}
141
 
142
		if (empty($server_list))
143
		{
144
			log_message('error', 'Session: Memcached server pool is empty.');
145
			return $this->_fail();
146
		}
147
 
148
		return $this->_success;
149
	}
150
 
151
	// ------------------------------------------------------------------------
152
 
153
	/**
154
	 * Read
155
	 *
156
	 * Reads session data and acquires a lock
157
	 *
158
	 * @param	string	$session_id	Session ID
159
	 * @return	string	Serialized session data
160
	 */
161
	public function read($session_id)
162
	{
163
		if (isset($this->_memcached) && $this->_get_lock($session_id))
164
		{
165
			// Needed by write() to detect session_regenerate_id() calls
166
			$this->_session_id = $session_id;
167
 
168
			$session_data = (string) $this->_memcached->get($this->_key_prefix.$session_id);
169
			$this->_fingerprint = md5($session_data);
170
			return $session_data;
171
		}
172
 
173
		return $this->_fail();
174
	}
175
 
176
	// ------------------------------------------------------------------------
177
 
178
	/**
179
	 * Write
180
	 *
181
	 * Writes (create / update) session data
182
	 *
183
	 * @param	string	$session_id	Session ID
184
	 * @param	string	$session_data	Serialized session data
185
	 * @return	bool
186
	 */
187
	public function write($session_id, $session_data)
188
	{
2049 lars 189
		if ( ! isset($this->_memcached, $this->_lock_key))
68 lars 190
		{
191
			return $this->_fail();
192
		}
193
		// Was the ID regenerated?
194
		elseif ($session_id !== $this->_session_id)
195
		{
196
			if ( ! $this->_release_lock() OR ! $this->_get_lock($session_id))
197
			{
198
				return $this->_fail();
199
			}
200
 
201
			$this->_fingerprint = md5('');
202
			$this->_session_id = $session_id;
203
		}
204
 
2049 lars 205
		$key = $this->_key_prefix.$session_id;
206
 
207
		$this->_memcached->replace($this->_lock_key, time(), 300);
208
		if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
68 lars 209
		{
2049 lars 210
			if ($this->_memcached->set($key, $session_data, $this->_config['expiration']))
68 lars 211
			{
2049 lars 212
				$this->_fingerprint = $fingerprint;
68 lars 213
				return $this->_success;
214
			}
2049 lars 215
 
216
			return $this->_fail();
68 lars 217
		}
2049 lars 218
		elseif (
219
			$this->_memcached->touch($key, $this->_config['expiration'])
220
			OR ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND && $this->_memcached->set($key, $session_data, $this->_config['expiration']))
221
		)
222
		{
223
			return $this->_success;
224
		}
68 lars 225
 
226
		return $this->_fail();
227
	}
228
 
229
	// ------------------------------------------------------------------------
230
 
231
	/**
232
	 * Close
233
	 *
234
	 * Releases locks and closes connection.
235
	 *
236
	 * @return	bool
237
	 */
238
	public function close()
239
	{
240
		if (isset($this->_memcached))
241
		{
242
			$this->_release_lock();
243
			if ( ! $this->_memcached->quit())
244
			{
245
				return $this->_fail();
246
			}
247
 
248
			$this->_memcached = NULL;
249
			return $this->_success;
250
		}
251
 
252
		return $this->_fail();
253
	}
254
 
255
	// ------------------------------------------------------------------------
256
 
257
	/**
258
	 * Destroy
259
	 *
260
	 * Destroys the current session.
261
	 *
262
	 * @param	string	$session_id	Session ID
263
	 * @return	bool
264
	 */
265
	public function destroy($session_id)
266
	{
267
		if (isset($this->_memcached, $this->_lock_key))
268
		{
269
			$this->_memcached->delete($this->_key_prefix.$session_id);
270
			$this->_cookie_destroy();
271
			return $this->_success;
272
		}
273
 
274
		return $this->_fail();
275
	}
276
 
277
	// ------------------------------------------------------------------------
278
 
279
	/**
280
	 * Garbage Collector
281
	 *
282
	 * Deletes expired sessions
283
	 *
284
	 * @param	int 	$maxlifetime	Maximum lifetime of sessions
285
	 * @return	bool
286
	 */
287
	public function gc($maxlifetime)
288
	{
289
		// Not necessary, Memcached takes care of that.
290
		return $this->_success;
291
	}
292
 
293
	// ------------------------------------------------------------------------
294
 
295
	/**
296
	 * Get lock
297
	 *
298
	 * Acquires an (emulated) lock.
299
	 *
300
	 * @param	string	$session_id	Session ID
301
	 * @return	bool
302
	 */
303
	protected function _get_lock($session_id)
304
	{
305
		// PHP 7 reuses the SessionHandler object on regeneration,
306
		// so we need to check here if the lock key is for the
307
		// correct session ID.
308
		if ($this->_lock_key === $this->_key_prefix.$session_id.':lock')
309
		{
310
			if ( ! $this->_memcached->replace($this->_lock_key, time(), 300))
311
			{
312
				return ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND)
2107 lars 313
					? $this->_memcached->add($this->_lock_key, time(), 300)
68 lars 314
					: FALSE;
315
			}
316
		}
317
 
318
		// 30 attempts to obtain a lock, in case another request already has it
319
		$lock_key = $this->_key_prefix.$session_id.':lock';
320
		$attempt = 0;
321
		do
322
		{
323
			if ($this->_memcached->get($lock_key))
324
			{
325
				sleep(1);
326
				continue;
327
			}
328
 
2107 lars 329
			$method = ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND) ? 'add' : 'set';
330
			if ( ! $this->_memcached->$method($lock_key, time(), 300))
68 lars 331
			{
332
				log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
333
				return FALSE;
334
			}
335
 
336
			$this->_lock_key = $lock_key;
337
			break;
338
		}
339
		while (++$attempt < 30);
340
 
341
		if ($attempt === 30)
342
		{
343
			log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.');
344
			return FALSE;
345
		}
346
 
347
		$this->_lock = TRUE;
348
		return TRUE;
349
	}
350
 
351
	// ------------------------------------------------------------------------
352
 
353
	/**
354
	 * Release lock
355
	 *
356
	 * Releases a previously acquired lock
357
	 *
358
	 * @return	bool
359
	 */
360
	protected function _release_lock()
361
	{
362
		if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
363
		{
364
			if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
365
			{
366
				log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key);
367
				return FALSE;
368
			}
369
 
370
			$this->_lock_key = NULL;
371
			$this->_lock = FALSE;
372
		}
373
 
374
		return TRUE;
375
	}
2049 lars 376
}