Subversion-Projekte lars-tiefland.ci

Revision

Revision 68 | Revision 2049 | 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
 *
9
 * Copyright (c) 2014 - 2016, 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 - 2016, 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
/**
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
	{
189
		if ( ! isset($this->_memcached))
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
 
205
		if (isset($this->_lock_key))
206
		{
207
			$key = $this->_key_prefix.$session_id;
208
 
209
			$this->_memcached->replace($this->_lock_key, time(), 300);
210
			if ($this->_fingerprint !== ($fingerprint = md5($session_data)))
211
			{
1257 lars 212
				if ($this->_memcached->set($key, $session_data, $this->_config['expiration']))
68 lars 213
				{
214
					$this->_fingerprint = $fingerprint;
215
					return $this->_success;
216
				}
217
 
218
				return $this->_fail();
219
			}
1257 lars 220
			elseif (
68 lars 221
				$this->_memcached->touch($key, $this->_config['expiration'])
222
				OR ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND && $this->_memcached->set($key, $session_data, $this->_config['expiration']))
223
			)
224
			{
225
				return $this->_success;
226
			}
227
		}
228
 
229
		return $this->_fail();
230
	}
231
 
232
	// ------------------------------------------------------------------------
233
 
234
	/**
235
	 * Close
236
	 *
237
	 * Releases locks and closes connection.
238
	 *
239
	 * @return	bool
240
	 */
241
	public function close()
242
	{
243
		if (isset($this->_memcached))
244
		{
245
			$this->_release_lock();
246
			if ( ! $this->_memcached->quit())
247
			{
248
				return $this->_fail();
249
			}
250
 
251
			$this->_memcached = NULL;
252
			return $this->_success;
253
		}
254
 
255
		return $this->_fail();
256
	}
257
 
258
	// ------------------------------------------------------------------------
259
 
260
	/**
261
	 * Destroy
262
	 *
263
	 * Destroys the current session.
264
	 *
265
	 * @param	string	$session_id	Session ID
266
	 * @return	bool
267
	 */
268
	public function destroy($session_id)
269
	{
270
		if (isset($this->_memcached, $this->_lock_key))
271
		{
272
			$this->_memcached->delete($this->_key_prefix.$session_id);
273
			$this->_cookie_destroy();
274
			return $this->_success;
275
		}
276
 
277
		return $this->_fail();
278
	}
279
 
280
	// ------------------------------------------------------------------------
281
 
282
	/**
283
	 * Garbage Collector
284
	 *
285
	 * Deletes expired sessions
286
	 *
287
	 * @param	int 	$maxlifetime	Maximum lifetime of sessions
288
	 * @return	bool
289
	 */
290
	public function gc($maxlifetime)
291
	{
292
		// Not necessary, Memcached takes care of that.
293
		return $this->_success;
294
	}
295
 
296
	// ------------------------------------------------------------------------
297
 
298
	/**
299
	 * Get lock
300
	 *
301
	 * Acquires an (emulated) lock.
302
	 *
303
	 * @param	string	$session_id	Session ID
304
	 * @return	bool
305
	 */
306
	protected function _get_lock($session_id)
307
	{
308
		// PHP 7 reuses the SessionHandler object on regeneration,
309
		// so we need to check here if the lock key is for the
310
		// correct session ID.
311
		if ($this->_lock_key === $this->_key_prefix.$session_id.':lock')
312
		{
313
			if ( ! $this->_memcached->replace($this->_lock_key, time(), 300))
314
			{
315
				return ($this->_memcached->getResultCode() === Memcached::RES_NOTFOUND)
316
					? $this->_memcached->set($this->_lock_key, time(), 300)
317
					: FALSE;
318
			}
319
		}
320
 
321
		// 30 attempts to obtain a lock, in case another request already has it
322
		$lock_key = $this->_key_prefix.$session_id.':lock';
323
		$attempt = 0;
324
		do
325
		{
326
			if ($this->_memcached->get($lock_key))
327
			{
328
				sleep(1);
329
				continue;
330
			}
331
 
332
			if ( ! $this->_memcached->set($lock_key, time(), 300))
333
			{
334
				log_message('error', 'Session: Error while trying to obtain lock for '.$this->_key_prefix.$session_id);
335
				return FALSE;
336
			}
337
 
338
			$this->_lock_key = $lock_key;
339
			break;
340
		}
341
		while (++$attempt < 30);
342
 
343
		if ($attempt === 30)
344
		{
345
			log_message('error', 'Session: Unable to obtain lock for '.$this->_key_prefix.$session_id.' after 30 attempts, aborting.');
346
			return FALSE;
347
		}
348
 
349
		$this->_lock = TRUE;
350
		return TRUE;
351
	}
352
 
353
	// ------------------------------------------------------------------------
354
 
355
	/**
356
	 * Release lock
357
	 *
358
	 * Releases a previously acquired lock
359
	 *
360
	 * @return	bool
361
	 */
362
	protected function _release_lock()
363
	{
364
		if (isset($this->_memcached, $this->_lock_key) && $this->_lock)
365
		{
366
			if ( ! $this->_memcached->delete($this->_lock_key) && $this->_memcached->getResultCode() !== Memcached::RES_NOTFOUND)
367
			{
368
				log_message('error', 'Session: Error while trying to free lock for '.$this->_lock_key);
369
				return FALSE;
370
			}
371
 
372
			$this->_lock_key = NULL;
373
			$this->_lock = FALSE;
374
		}
375
 
376
		return TRUE;
377
	}
378
}