|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* PHP 5.4 defines SessionHandlerInterface, but PHP 5.3 doesn't. For backwards compatibility, if it doesn't exist |
|
5
|
|
|
* (and no other fallback exists in other libraries) then define it. |
|
6
|
|
|
* |
|
7
|
|
|
* Then, either way, add a new function "register_sessionhandler" which takes a SessionHandlerInterface and |
|
8
|
|
|
* registers it (including registering session_write_close as a shutdown function) |
|
9
|
|
|
*/ |
|
10
|
|
|
if (!interface_exists('SessionHandlerInterface')) { |
|
11
|
|
|
interface SessionHandlerInterface |
|
|
|
|
|
|
12
|
|
|
{ |
|
13
|
|
|
/* Methods */ |
|
14
|
|
|
public function close(); |
|
15
|
|
|
public function destroy($session_id); |
|
16
|
|
|
public function gc($maxlifetime); |
|
17
|
|
|
public function open($save_path, $name); |
|
18
|
|
|
public function read($session_id); |
|
19
|
|
|
public function write($session_id, $session_data); |
|
20
|
|
|
} |
|
21
|
|
|
} |
|
22
|
|
|
|
|
23
|
|
|
if (version_compare(PHP_VERSION, '5.4.0', '<')) { |
|
24
|
|
|
function register_sessionhandler($handler) |
|
25
|
|
|
{ |
|
26
|
|
|
session_set_save_handler( |
|
27
|
|
|
array($handler, 'open'), |
|
28
|
|
|
array($handler, 'close'), |
|
29
|
|
|
array($handler, 'read'), |
|
30
|
|
|
array($handler, 'write'), |
|
31
|
|
|
array($handler, 'destroy'), |
|
32
|
|
|
array($handler, 'gc') |
|
33
|
|
|
); |
|
34
|
|
|
|
|
35
|
|
|
register_shutdown_function('session_write_close'); |
|
36
|
|
|
} |
|
37
|
|
|
} else { |
|
38
|
|
|
function register_sessionhandler($handler) |
|
|
|
|
|
|
39
|
|
|
{ |
|
40
|
|
|
session_set_save_handler($handler, true); |
|
41
|
|
|
} |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Class HybridSessionStore_Crypto |
|
46
|
|
|
* Some cryptography used for Session cookie encryption. Requires the mcrypt extension. |
|
47
|
|
|
* |
|
48
|
|
|
*/ |
|
49
|
|
|
class HybridSessionStore_Crypto |
|
|
|
|
|
|
50
|
|
|
{ |
|
51
|
|
|
|
|
52
|
|
|
private $key; |
|
53
|
|
|
private $ivSize; |
|
54
|
|
|
private $keySize; |
|
55
|
|
|
|
|
56
|
|
|
public $salt; |
|
57
|
|
|
private $saltedKey; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* @param $key a per-site secret string which is used as the base encryption key. |
|
61
|
|
|
* @param $salt a per-session random string which is used as a salt to generate a per-session key |
|
62
|
|
|
* |
|
63
|
|
|
* The base encryption key needs to stay secret. If an attacker ever gets it, they can read their session, |
|
64
|
|
|
* and even modify & re-sign it. |
|
65
|
|
|
* |
|
66
|
|
|
* The salt is a random per-session string that is used with the base encryption key to create a per-session key. |
|
67
|
|
|
* This (amongst other things) makes sure an attacker can't use a known-plaintext attack to guess the key. |
|
68
|
|
|
* |
|
69
|
|
|
* Normally we could create a salt on encryption, send it to the client as part of the session (it doesn't |
|
70
|
|
|
* need to remain secret), then use the returned salt to decrypt. But we already have the Session ID which makes |
|
71
|
|
|
* a great salt, so no need to generate & handle another one. |
|
72
|
|
|
*/ |
|
73
|
|
|
public function __construct($key, $salt) |
|
74
|
|
|
{ |
|
75
|
|
|
$this->ivSize = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); |
|
76
|
|
|
$this->keySize = mcrypt_get_key_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC); |
|
77
|
|
|
|
|
78
|
|
|
$this->key = $key; |
|
79
|
|
|
$this->salt = $salt; |
|
80
|
|
|
$this->saltedKey = hash_pbkdf2('sha256', $this->key, $this->salt, 1000, $this->keySize, true); |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* Encrypt and then sign some cleartext |
|
85
|
|
|
* |
|
86
|
|
|
* @param $cleartext - The cleartext to encrypt and sign |
|
87
|
|
|
* @return string - The encrypted-and-signed message as base64 ASCII. |
|
88
|
|
|
*/ |
|
89
|
|
|
public function encrypt($cleartext) |
|
90
|
|
|
{ |
|
91
|
|
|
$iv = mcrypt_create_iv($this->ivSize, MCRYPT_DEV_URANDOM); |
|
92
|
|
|
|
|
93
|
|
|
$enc = mcrypt_encrypt( |
|
94
|
|
|
MCRYPT_RIJNDAEL_256, |
|
95
|
|
|
$this->saltedKey, |
|
96
|
|
|
$cleartext, |
|
97
|
|
|
MCRYPT_MODE_CBC, |
|
98
|
|
|
$iv |
|
99
|
|
|
); |
|
100
|
|
|
|
|
101
|
|
|
$hash = hash_hmac('sha256', $enc, $this->saltedKey); |
|
102
|
|
|
|
|
103
|
|
|
return base64_encode($iv.$hash.$enc); |
|
104
|
|
|
} |
|
105
|
|
|
|
|
106
|
|
|
/** |
|
107
|
|
|
* Check the signature on an encrypted-and-signed message, and if valid decrypt the content |
|
108
|
|
|
* |
|
109
|
|
|
* @param $data - The encrypted-and-signed message as base64 ASCII |
|
110
|
|
|
* @return bool|string - The decrypted cleartext or false if signature failed |
|
111
|
|
|
*/ |
|
112
|
|
|
public function decrypt($data) |
|
113
|
|
|
{ |
|
114
|
|
|
$data = base64_decode($data); |
|
115
|
|
|
|
|
116
|
|
|
$iv = substr($data, 0, $this->ivSize); |
|
117
|
|
|
$hash = substr($data, $this->ivSize, 64); |
|
118
|
|
|
$enc = substr($data, $this->ivSize + 64); |
|
119
|
|
|
|
|
120
|
|
|
$cleartext = rtrim(mcrypt_decrypt( |
|
121
|
|
|
MCRYPT_RIJNDAEL_256, |
|
122
|
|
|
$this->saltedKey, |
|
123
|
|
|
$enc, |
|
124
|
|
|
MCRYPT_MODE_CBC, |
|
125
|
|
|
$iv |
|
126
|
|
|
), "\x00"); |
|
127
|
|
|
|
|
128
|
|
|
// Needs to be after decrypt so it always runs, to avoid timing attack |
|
129
|
|
|
$gen_hash = hash_hmac('sha256', $enc, $this->saltedKey); |
|
130
|
|
|
|
|
131
|
|
|
if ($gen_hash == $hash) { |
|
132
|
|
|
return $cleartext; |
|
133
|
|
|
} |
|
134
|
|
|
return false; |
|
135
|
|
|
} |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
abstract class HybridSessionStore_Base implements SessionHandlerInterface |
|
|
|
|
|
|
139
|
|
|
{ |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* Session secret key |
|
143
|
|
|
* |
|
144
|
|
|
* @var string |
|
145
|
|
|
*/ |
|
146
|
|
|
protected $key = null; |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Assign a new session secret key |
|
150
|
|
|
* |
|
151
|
|
|
* @param string $key |
|
152
|
|
|
*/ |
|
153
|
|
|
public function setKey($key) |
|
154
|
|
|
{ |
|
155
|
|
|
$this->key = $key; |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
/** |
|
159
|
|
|
* Get the session secret key |
|
160
|
|
|
* |
|
161
|
|
|
* @return string |
|
162
|
|
|
*/ |
|
163
|
|
|
protected function getKey() |
|
164
|
|
|
{ |
|
165
|
|
|
return $this->key; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
/** |
|
169
|
|
|
* Get lifetime in number of seconds |
|
170
|
|
|
* |
|
171
|
|
|
* @return int |
|
172
|
|
|
*/ |
|
173
|
|
|
protected function getLifetime() |
|
174
|
|
|
{ |
|
175
|
|
|
$params = session_get_cookie_params(); |
|
176
|
|
|
$cookieLifetime = (int)$params['lifetime']; |
|
177
|
|
|
$gcLifetime = (int)ini_get('session.gc_maxlifetime'); |
|
178
|
|
|
return $cookieLifetime ? min($cookieLifetime, $gcLifetime) : $gcLifetime; |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* Gets the current unix timestamp |
|
183
|
|
|
* |
|
184
|
|
|
* @return int |
|
185
|
|
|
*/ |
|
186
|
|
|
protected function getNow() |
|
187
|
|
|
{ |
|
188
|
|
|
return (int)SS_Datetime::now()->Format('U'); |
|
189
|
|
|
} |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
/** |
|
193
|
|
|
* Class HybridSessionStore_Cookie |
|
194
|
|
|
* |
|
195
|
|
|
* A session store which stores the session data in an encrypted & signed cookie. |
|
196
|
|
|
* |
|
197
|
|
|
* This way the server doesn't need to open a database connection or have a shared filesystem for reading |
|
198
|
|
|
* the session from - the client passes through the session with every request. |
|
199
|
|
|
* |
|
200
|
|
|
* This approach does have some limitations - cookies can only be quite small (4K total, but we limit to 1K) |
|
201
|
|
|
* and can only be set _before_ the server starts sending a response. |
|
202
|
|
|
* |
|
203
|
|
|
* So we clear the cookie on Session startup (which should always be before the headers get sent), but just |
|
204
|
|
|
* fail on Session write if we can't use cookies, assuming there's something watching for that & providing a fallback |
|
205
|
|
|
*/ |
|
206
|
|
|
class HybridSessionStore_Cookie extends HybridSessionStore_Base |
|
|
|
|
|
|
207
|
|
|
{ |
|
208
|
|
|
|
|
209
|
|
|
/** |
|
210
|
|
|
* Maximum length of a cookie value in characters |
|
211
|
|
|
* |
|
212
|
|
|
* @var int |
|
213
|
|
|
* @config |
|
214
|
|
|
*/ |
|
215
|
|
|
private static $max_length = 1024; |
|
|
|
|
|
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* Encryption service |
|
219
|
|
|
* |
|
220
|
|
|
* @var HybridSessionStore_Crypto |
|
221
|
|
|
*/ |
|
222
|
|
|
protected $crypto; |
|
223
|
|
|
|
|
224
|
|
|
/** |
|
225
|
|
|
* Name of cookie |
|
226
|
|
|
* |
|
227
|
|
|
* @var string |
|
228
|
|
|
*/ |
|
229
|
|
|
protected $cookie; |
|
230
|
|
|
|
|
231
|
|
|
/** |
|
232
|
|
|
* Known unmodified value of this cookie. If the cookie backend has been read into the application, |
|
233
|
|
|
* then the backend is unable to verify the modification state of this value internally within the |
|
234
|
|
|
* system, so this will be left null unless written back. |
|
235
|
|
|
* |
|
236
|
|
|
* If the content exceeds max_length then the backend can also not maintain this cookie, also |
|
237
|
|
|
* setting this variable to null. |
|
238
|
|
|
* |
|
239
|
|
|
* @var string |
|
240
|
|
|
*/ |
|
241
|
|
|
protected $currentCookieData; |
|
242
|
|
|
|
|
243
|
|
|
public function open($save_path, $name) |
|
244
|
|
|
{ |
|
245
|
|
|
$this->cookie = $name.'_2'; |
|
246
|
|
|
// Read the incoming value, then clear the cookie - we might not be able |
|
247
|
|
|
// to do so later if write() is called after headers are sent |
|
248
|
|
|
// This is intended to force a failover to the database store if the |
|
249
|
|
|
// modified session cannot be emitted. |
|
250
|
|
|
$this->currentCookieData = Cookie::get($this->cookie); |
|
251
|
|
|
if ($this->currentCookieData) { |
|
|
|
|
|
|
252
|
|
|
Cookie::set($this->cookie, ''); |
|
253
|
|
|
} |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
public function close() |
|
257
|
|
|
{ |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
/** |
|
261
|
|
|
* Get the cryptography store for the specified session |
|
262
|
|
|
* |
|
263
|
|
|
* @param string $session_id |
|
264
|
|
|
* @return HybridSessionStore_Crypto |
|
265
|
|
|
*/ |
|
266
|
|
|
protected function getCrypto($session_id) |
|
267
|
|
|
{ |
|
268
|
|
|
$key = $this->getKey(); |
|
269
|
|
|
if (!$key) { |
|
270
|
|
|
return null; |
|
271
|
|
|
} |
|
272
|
|
|
if (!$this->crypto || $this->crypto->salt != $session_id) { |
|
273
|
|
|
$this->crypto = new HybridSessionStore_Crypto($key, $session_id); |
|
274
|
|
|
} |
|
275
|
|
|
return $this->crypto; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
public function read($session_id) |
|
279
|
|
|
{ |
|
280
|
|
|
// Check ability to safely decrypt content |
|
281
|
|
|
if (!$this->currentCookieData |
|
282
|
|
|
|| !($crypto = $this->getCrypto($session_id)) |
|
283
|
|
|
) { |
|
284
|
|
|
return; |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
// Decrypt and invalidate old data |
|
288
|
|
|
$cookieData = $crypto->decrypt($this->currentCookieData); |
|
289
|
|
|
$this->currentCookieData = null; |
|
290
|
|
|
|
|
291
|
|
|
// Verify expiration |
|
292
|
|
|
if ($cookieData) { |
|
|
|
|
|
|
293
|
|
|
$expiry = (int)substr($cookieData, 0, 10); |
|
294
|
|
|
$data = substr($cookieData, 10); |
|
295
|
|
|
|
|
296
|
|
|
if ($expiry > $this->getNow()) { |
|
297
|
|
|
return $data; |
|
298
|
|
|
} |
|
299
|
|
|
} |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
/** |
|
303
|
|
|
* Determine if the session could be verifably written to cookie storage |
|
304
|
|
|
* |
|
305
|
|
|
* @return bool |
|
306
|
|
|
*/ |
|
307
|
|
|
protected function canWrite() |
|
308
|
|
|
{ |
|
309
|
|
|
return !headers_sent(); |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
public function write($session_id, $session_data) |
|
313
|
|
|
{ |
|
314
|
|
|
// Check ability to safely encrypt and write content |
|
315
|
|
|
if (!$this->canWrite() |
|
316
|
|
|
|| (strlen($session_data) > Config::inst()->get(__CLASS__, 'max_length')) |
|
317
|
|
|
|| !($crypto = $this->getCrypto($session_id)) |
|
318
|
|
|
) { |
|
319
|
|
|
return false; |
|
320
|
|
|
} |
|
321
|
|
|
|
|
322
|
|
|
// Prepare content for write |
|
323
|
|
|
$params = session_get_cookie_params(); |
|
324
|
|
|
// Total max lifetime, stored internally |
|
325
|
|
|
$lifetime = $this->getLifetime(); |
|
326
|
|
|
$expiry = $this->getNow() + $lifetime; |
|
327
|
|
|
|
|
328
|
|
|
// Restore the known good cookie value |
|
329
|
|
|
$this->currentCookieData = $this->crypto->encrypt( |
|
330
|
|
|
sprintf('%010u', $expiry) . $session_data |
|
331
|
|
|
); |
|
332
|
|
|
|
|
333
|
|
|
// Respect auto-expire on browser close for the session cookie (in case the cookie lifetime is zero) |
|
334
|
|
|
$cookieLifetime = min((int)$params['lifetime'], $lifetime); |
|
335
|
|
|
Cookie::set( |
|
336
|
|
|
$this->cookie, |
|
337
|
|
|
$this->currentCookieData, |
|
338
|
|
|
$cookieLifetime / 86400, |
|
339
|
|
|
$params['path'], |
|
340
|
|
|
$params['domain'], |
|
341
|
|
|
$params['secure'], |
|
342
|
|
|
$params['httponly'] |
|
343
|
|
|
); |
|
344
|
|
|
|
|
345
|
|
|
return true; |
|
346
|
|
|
} |
|
347
|
|
|
|
|
348
|
|
|
public function destroy($session_id) |
|
349
|
|
|
{ |
|
350
|
|
|
$this->currentCookieData = null; |
|
351
|
|
|
Cookie::force_expiry($this->cookie); |
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
public function gc($maxlifetime) |
|
355
|
|
|
{ |
|
356
|
|
|
// NOP |
|
357
|
|
|
} |
|
358
|
|
|
} |
|
359
|
|
|
|
|
360
|
|
|
class HybridSessionStore_Database extends HybridSessionStore_Base |
|
|
|
|
|
|
361
|
|
|
{ |
|
362
|
|
|
|
|
363
|
|
|
/** |
|
364
|
|
|
* Determine if the DB is ready to use. |
|
365
|
|
|
* |
|
366
|
|
|
* @return bool |
|
367
|
|
|
* @throws Exception |
|
368
|
|
|
*/ |
|
369
|
|
|
protected function isDatabaseReady() |
|
370
|
|
|
{ |
|
371
|
|
|
// Such as during setup of testsession prior to DB connection. |
|
372
|
|
|
if (!DB::isActive()) { |
|
|
|
|
|
|
373
|
|
|
return false; |
|
374
|
|
|
} |
|
375
|
|
|
|
|
376
|
|
|
// If we have a DB of the wrong type then complain |
|
377
|
|
|
if (!(DB::getConn() instanceof MySQLDatabase)) { |
|
|
|
|
|
|
378
|
|
|
throw new Exception('HybridSessionStore currently only works with MySQL databases'); |
|
379
|
|
|
} |
|
380
|
|
|
|
|
381
|
|
|
// Prevent freakout during dev/build |
|
382
|
|
|
return ClassInfo::hasTable('HybridSessionDataObject'); |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
public function open($save_path, $name) |
|
386
|
|
|
{ |
|
387
|
|
|
} |
|
388
|
|
|
|
|
389
|
|
|
public function close() |
|
390
|
|
|
{ |
|
391
|
|
|
} |
|
392
|
|
|
|
|
393
|
|
|
public function read($session_id) |
|
394
|
|
|
{ |
|
395
|
|
|
if (!$this->isDatabaseReady()) { |
|
396
|
|
|
return null; |
|
397
|
|
|
} |
|
398
|
|
|
|
|
399
|
|
|
$result = DB::query(sprintf( |
|
400
|
|
|
'SELECT "Data" FROM "HybridSessionDataObject" |
|
401
|
|
|
WHERE "SessionID" = \'%s\' AND "Expiry" >= %u', |
|
402
|
|
|
Convert::raw2sql($session_id), |
|
403
|
|
|
$this->getNow() |
|
404
|
|
|
)); |
|
405
|
|
|
|
|
406
|
|
|
if ($result && $result->numRecords()) { |
|
407
|
|
|
$data = $result->first(); |
|
408
|
|
|
return $data['Data']; |
|
409
|
|
|
} |
|
410
|
|
|
} |
|
411
|
|
|
|
|
412
|
|
|
public function write($session_id, $session_data) |
|
413
|
|
|
{ |
|
414
|
|
|
if (!$this->isDatabaseReady()) { |
|
415
|
|
|
return false; |
|
416
|
|
|
} |
|
417
|
|
|
|
|
418
|
|
|
$expiry = $this->getNow() + $this->getLifetime(); |
|
419
|
|
|
DB::query($str = sprintf( |
|
420
|
|
|
'INSERT INTO "HybridSessionDataObject" ("SessionID", "Expiry", "Data") |
|
421
|
|
|
VALUES (\'%1$s\', %2$u, \'%3$s\') |
|
422
|
|
|
ON DUPLICATE KEY UPDATE "Expiry" = %2$u, "Data" = \'%3$s\'', |
|
423
|
|
|
Convert::raw2sql($session_id), |
|
424
|
|
|
$expiry, |
|
425
|
|
|
Convert::raw2sql($session_data) |
|
426
|
|
|
)); |
|
427
|
|
|
|
|
428
|
|
|
return true; |
|
429
|
|
|
} |
|
430
|
|
|
|
|
431
|
|
|
public function destroy($session_id) |
|
432
|
|
|
{ |
|
433
|
|
|
// NOP |
|
434
|
|
|
} |
|
435
|
|
|
|
|
436
|
|
|
public function gc($maxlifetime) |
|
437
|
|
|
{ |
|
438
|
|
|
if (!$this->isDatabaseReady()) { |
|
439
|
|
|
return; |
|
440
|
|
|
} |
|
441
|
|
|
DB::query(sprintf( |
|
442
|
|
|
'DELETE FROM "HybridSessionDataObject" WHERE "Expiry" < %u', |
|
443
|
|
|
$this->getNow() |
|
444
|
|
|
)); |
|
445
|
|
|
} |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
|
|
449
|
|
|
class HybridSessionStore extends HybridSessionStore_Base |
|
|
|
|
|
|
450
|
|
|
{ |
|
451
|
|
|
|
|
452
|
|
|
/** |
|
453
|
|
|
* List of session handlers |
|
454
|
|
|
* |
|
455
|
|
|
* @var array[HybridSessionStore_Base] |
|
456
|
|
|
*/ |
|
457
|
|
|
protected $handlers = array(); |
|
458
|
|
|
|
|
459
|
|
|
/** |
|
460
|
|
|
* True if this session store has been initialised |
|
461
|
|
|
* |
|
462
|
|
|
* @var bool |
|
463
|
|
|
*/ |
|
464
|
|
|
protected static $enabled = false; |
|
465
|
|
|
|
|
466
|
|
|
/** |
|
467
|
|
|
* @param array[HybridSessionStore_Base] |
|
468
|
|
|
*/ |
|
469
|
|
|
public function setHandlers($handlers) |
|
470
|
|
|
{ |
|
471
|
|
|
$this->handlers = $handlers; |
|
472
|
|
|
$this->setKey($this->getKey()); |
|
473
|
|
|
} |
|
474
|
|
|
|
|
475
|
|
|
public function setKey($key) |
|
476
|
|
|
{ |
|
477
|
|
|
parent::setKey($key); |
|
478
|
|
|
foreach ($this->handlers as $handler) { |
|
479
|
|
|
$handler->setKey($key); |
|
480
|
|
|
} |
|
481
|
|
|
} |
|
482
|
|
|
|
|
483
|
|
|
/** |
|
484
|
|
|
* @return array[SessionHandlerInterface] |
|
|
|
|
|
|
485
|
|
|
*/ |
|
486
|
|
|
public function getHandlers() |
|
487
|
|
|
{ |
|
488
|
|
|
return $this->handlers; |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
public function open($save_path, $name) |
|
492
|
|
|
{ |
|
493
|
|
|
foreach ($this->handlers as $handler) { |
|
494
|
|
|
$handler->open($save_path, $name); |
|
495
|
|
|
} |
|
496
|
|
|
|
|
497
|
|
|
return true; |
|
498
|
|
|
} |
|
499
|
|
|
|
|
500
|
|
|
public function close() |
|
501
|
|
|
{ |
|
502
|
|
|
foreach ($this->handlers as $handler) { |
|
503
|
|
|
$handler->close(); |
|
504
|
|
|
} |
|
505
|
|
|
|
|
506
|
|
|
return true; |
|
507
|
|
|
} |
|
508
|
|
|
|
|
509
|
|
|
public function read($session_id) |
|
510
|
|
|
{ |
|
511
|
|
|
foreach ($this->handlers as $handler) { |
|
512
|
|
|
if ($data = $handler->read($session_id)) { |
|
513
|
|
|
return $data; |
|
514
|
|
|
} |
|
515
|
|
|
} |
|
516
|
|
|
|
|
517
|
|
|
return ''; |
|
518
|
|
|
} |
|
519
|
|
|
|
|
520
|
|
|
public function write($session_id, $session_data) |
|
521
|
|
|
{ |
|
522
|
|
|
foreach ($this->handlers as $handler) { |
|
523
|
|
|
if ($handler->write($session_id, $session_data)) { |
|
524
|
|
|
return; |
|
525
|
|
|
} |
|
526
|
|
|
} |
|
527
|
|
|
} |
|
528
|
|
|
|
|
529
|
|
|
public function destroy($session_id) |
|
530
|
|
|
{ |
|
531
|
|
|
foreach ($this->handlers as $handler) { |
|
532
|
|
|
$handler->destroy($session_id); |
|
533
|
|
|
} |
|
534
|
|
|
} |
|
535
|
|
|
|
|
536
|
|
|
public function gc($maxlifetime) |
|
537
|
|
|
{ |
|
538
|
|
|
foreach ($this->handlers as $handler) { |
|
539
|
|
|
$handler->gc($maxlifetime); |
|
540
|
|
|
} |
|
541
|
|
|
} |
|
542
|
|
|
|
|
543
|
|
|
/** |
|
544
|
|
|
* Register the session handler as the default |
|
545
|
|
|
* |
|
546
|
|
|
* @param string $key Desired session key |
|
547
|
|
|
*/ |
|
548
|
|
|
public static function init($key = null) |
|
549
|
|
|
{ |
|
550
|
|
|
$instance = Injector::inst()->get(__CLASS__); |
|
551
|
|
|
if (empty($key)) { |
|
552
|
|
|
user_error( |
|
553
|
|
|
'HybridSessionStore::init() was not given a $key. Disabling cookie-based storage', |
|
554
|
|
|
E_USER_WARNING |
|
555
|
|
|
); |
|
556
|
|
|
} else { |
|
557
|
|
|
$instance->setKey($key); |
|
558
|
|
|
} |
|
559
|
|
|
register_sessionhandler($instance); |
|
560
|
|
|
self::$enabled = true; |
|
561
|
|
|
} |
|
562
|
|
|
|
|
563
|
|
|
public static function is_enabled() |
|
564
|
|
|
{ |
|
565
|
|
|
return self::$enabled; |
|
566
|
|
|
} |
|
567
|
|
|
} |
|
568
|
|
|
|
|
569
|
|
|
class HybridSessionStore_RequestFilter implements RequestFilter |
|
|
|
|
|
|
570
|
|
|
{ |
|
571
|
|
|
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) |
|
572
|
|
|
{ |
|
573
|
|
|
// NOP |
|
574
|
|
|
} |
|
575
|
|
|
|
|
576
|
|
|
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model) |
|
577
|
|
|
{ |
|
578
|
|
|
if (HybridSessionStore::is_enabled()) { |
|
579
|
|
|
session_write_close(); |
|
580
|
|
|
} |
|
581
|
|
|
} |
|
582
|
|
|
} |
|
583
|
|
|
|
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.