6416a03fdf4b5c9a60cd23552434286e5e2bf4b7
[yaffs-website] / vendor / symfony / http-foundation / Session / Storage / NativeSessionStorage.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\HttpFoundation\Session\Storage;
13
14 use Symfony\Component\HttpFoundation\Session\SessionBagInterface;
15 use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
16 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
17 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
18
19 /**
20  * This provides a base class for session attribute storage.
21  *
22  * @author Drak <drak@zikula.org>
23  */
24 class NativeSessionStorage implements SessionStorageInterface
25 {
26     /**
27      * @var SessionBagInterface[]
28      */
29     protected $bags = array();
30
31     /**
32      * @var bool
33      */
34     protected $started = false;
35
36     /**
37      * @var bool
38      */
39     protected $closed = false;
40
41     /**
42      * @var AbstractProxy|\SessionHandlerInterface
43      */
44     protected $saveHandler;
45
46     /**
47      * @var MetadataBag
48      */
49     protected $metadataBag;
50
51     /**
52      * Depending on how you want the storage driver to behave you probably
53      * want to override this constructor entirely.
54      *
55      * List of options for $options array with their defaults.
56      *
57      * @see http://php.net/session.configuration for options
58      * but we omit 'session.' from the beginning of the keys for convenience.
59      *
60      * ("auto_start", is not supported as it tells PHP to start a session before
61      * PHP starts to execute user-land code. Setting during runtime has no effect).
62      *
63      * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
64      * cache_expire, "0"
65      * cookie_domain, ""
66      * cookie_httponly, ""
67      * cookie_lifetime, "0"
68      * cookie_path, "/"
69      * cookie_secure, ""
70      * entropy_file, ""
71      * entropy_length, "0"
72      * gc_divisor, "100"
73      * gc_maxlifetime, "1440"
74      * gc_probability, "1"
75      * hash_bits_per_character, "4"
76      * hash_function, "0"
77      * lazy_write, "1"
78      * name, "PHPSESSID"
79      * referer_check, ""
80      * serialize_handler, "php"
81      * use_strict_mode, "0"
82      * use_cookies, "1"
83      * use_only_cookies, "1"
84      * use_trans_sid, "0"
85      * upload_progress.enabled, "1"
86      * upload_progress.cleanup, "1"
87      * upload_progress.prefix, "upload_progress_"
88      * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS"
89      * upload_progress.freq, "1%"
90      * upload_progress.min-freq, "1"
91      * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset="
92      * sid_length, "32"
93      * sid_bits_per_character, "5"
94      * trans_sid_hosts, $_SERVER['HTTP_HOST']
95      * trans_sid_tags, "a=href,area=href,frame=src,form="
96      *
97      * @param array                         $options Session configuration options
98      * @param \SessionHandlerInterface|null $handler
99      * @param MetadataBag                   $metaBag MetadataBag
100      */
101     public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null)
102     {
103         $options += array(
104             'cache_limiter' => '',
105             'cache_expire' => 0,
106             'use_cookies' => 1,
107             'lazy_write' => 1,
108         );
109
110         session_register_shutdown();
111
112         $this->setMetadataBag($metaBag);
113         $this->setOptions($options);
114         $this->setSaveHandler($handler);
115     }
116
117     /**
118      * Gets the save handler instance.
119      *
120      * @return AbstractProxy|\SessionHandlerInterface
121      */
122     public function getSaveHandler()
123     {
124         return $this->saveHandler;
125     }
126
127     /**
128      * {@inheritdoc}
129      */
130     public function start()
131     {
132         if ($this->started) {
133             return true;
134         }
135
136         if (\PHP_SESSION_ACTIVE === session_status()) {
137             throw new \RuntimeException('Failed to start the session: already started by PHP.');
138         }
139
140         if (ini_get('session.use_cookies') && headers_sent($file, $line)) {
141             throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
142         }
143
144         // ok to try and start the session
145         if (!session_start()) {
146             throw new \RuntimeException('Failed to start the session');
147         }
148
149         $this->loadSession();
150
151         return true;
152     }
153
154     /**
155      * {@inheritdoc}
156      */
157     public function getId()
158     {
159         return $this->saveHandler->getId();
160     }
161
162     /**
163      * {@inheritdoc}
164      */
165     public function setId($id)
166     {
167         $this->saveHandler->setId($id);
168     }
169
170     /**
171      * {@inheritdoc}
172      */
173     public function getName()
174     {
175         return $this->saveHandler->getName();
176     }
177
178     /**
179      * {@inheritdoc}
180      */
181     public function setName($name)
182     {
183         $this->saveHandler->setName($name);
184     }
185
186     /**
187      * {@inheritdoc}
188      */
189     public function regenerate($destroy = false, $lifetime = null)
190     {
191         // Cannot regenerate the session ID for non-active sessions.
192         if (\PHP_SESSION_ACTIVE !== session_status()) {
193             return false;
194         }
195
196         if (headers_sent()) {
197             return false;
198         }
199
200         if (null !== $lifetime) {
201             ini_set('session.cookie_lifetime', $lifetime);
202         }
203
204         if ($destroy) {
205             $this->metadataBag->stampNew();
206         }
207
208         $isRegenerated = session_regenerate_id($destroy);
209
210         // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it.
211         // @see https://bugs.php.net/bug.php?id=70013
212         $this->loadSession();
213
214         return $isRegenerated;
215     }
216
217     /**
218      * {@inheritdoc}
219      */
220     public function save()
221     {
222         $session = $_SESSION;
223
224         foreach ($this->bags as $bag) {
225             if (empty($_SESSION[$key = $bag->getStorageKey()])) {
226                 unset($_SESSION[$key]);
227             }
228         }
229         if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) {
230             unset($_SESSION[$key]);
231         }
232
233         // Register custom error handler to catch a possible failure warning during session write
234         set_error_handler(function ($errno, $errstr, $errfile, $errline) {
235             throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline);
236         }, E_WARNING);
237
238         try {
239             $e = null;
240             session_write_close();
241         } catch (\ErrorException $e) {
242         } finally {
243             restore_error_handler();
244             $_SESSION = $session;
245         }
246         if (null !== $e) {
247             // The default PHP error message is not very helpful, as it does not give any information on the current save handler.
248             // Therefore, we catch this error and trigger a warning with a better error message
249             $handler = $this->getSaveHandler();
250             if ($handler instanceof SessionHandlerProxy) {
251                 $handler = $handler->getHandler();
252             }
253
254             trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING);
255         }
256
257         $this->closed = true;
258         $this->started = false;
259     }
260
261     /**
262      * {@inheritdoc}
263      */
264     public function clear()
265     {
266         // clear out the bags
267         foreach ($this->bags as $bag) {
268             $bag->clear();
269         }
270
271         // clear out the session
272         $_SESSION = array();
273
274         // reconnect the bags to the session
275         $this->loadSession();
276     }
277
278     /**
279      * {@inheritdoc}
280      */
281     public function registerBag(SessionBagInterface $bag)
282     {
283         if ($this->started) {
284             throw new \LogicException('Cannot register a bag when the session is already started.');
285         }
286
287         $this->bags[$bag->getName()] = $bag;
288     }
289
290     /**
291      * {@inheritdoc}
292      */
293     public function getBag($name)
294     {
295         if (!isset($this->bags[$name])) {
296             throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name));
297         }
298
299         if (!$this->started && $this->saveHandler->isActive()) {
300             $this->loadSession();
301         } elseif (!$this->started) {
302             $this->start();
303         }
304
305         return $this->bags[$name];
306     }
307
308     public function setMetadataBag(MetadataBag $metaBag = null)
309     {
310         if (null === $metaBag) {
311             $metaBag = new MetadataBag();
312         }
313
314         $this->metadataBag = $metaBag;
315     }
316
317     /**
318      * Gets the MetadataBag.
319      *
320      * @return MetadataBag
321      */
322     public function getMetadataBag()
323     {
324         return $this->metadataBag;
325     }
326
327     /**
328      * {@inheritdoc}
329      */
330     public function isStarted()
331     {
332         return $this->started;
333     }
334
335     /**
336      * Sets session.* ini variables.
337      *
338      * For convenience we omit 'session.' from the beginning of the keys.
339      * Explicitly ignores other ini keys.
340      *
341      * @param array $options Session ini directives array(key => value)
342      *
343      * @see http://php.net/session.configuration
344      */
345     public function setOptions(array $options)
346     {
347         if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
348             return;
349         }
350
351         $validOptions = array_flip(array(
352             'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
353             'cookie_lifetime', 'cookie_path', 'cookie_secure',
354             'entropy_file', 'entropy_length', 'gc_divisor',
355             'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character',
356             'hash_function', 'lazy_write', 'name', 'referer_check',
357             'serialize_handler', 'use_strict_mode', 'use_cookies',
358             'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
359             'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
360             'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
361             'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
362         ));
363
364         foreach ($options as $key => $value) {
365             if (isset($validOptions[$key])) {
366                 ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
367             }
368         }
369     }
370
371     /**
372      * Registers session save handler as a PHP session handler.
373      *
374      * To use internal PHP session save handlers, override this method using ini_set with
375      * session.save_handler and session.save_path e.g.
376      *
377      *     ini_set('session.save_handler', 'files');
378      *     ini_set('session.save_path', '/tmp');
379      *
380      * or pass in a \SessionHandler instance which configures session.save_handler in the
381      * constructor, for a template see NativeFileSessionHandler or use handlers in
382      * composer package drak/native-session
383      *
384      * @see http://php.net/session-set-save-handler
385      * @see http://php.net/sessionhandlerinterface
386      * @see http://php.net/sessionhandler
387      * @see http://github.com/drak/NativeSession
388      *
389      * @param \SessionHandlerInterface|null $saveHandler
390      *
391      * @throws \InvalidArgumentException
392      */
393     public function setSaveHandler($saveHandler = null)
394     {
395         if (!$saveHandler instanceof AbstractProxy &&
396             !$saveHandler instanceof \SessionHandlerInterface &&
397             null !== $saveHandler) {
398             throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
399         }
400
401         // Wrap $saveHandler in proxy and prevent double wrapping of proxy
402         if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
403             $saveHandler = new SessionHandlerProxy($saveHandler);
404         } elseif (!$saveHandler instanceof AbstractProxy) {
405             $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
406         }
407         $this->saveHandler = $saveHandler;
408
409         if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
410             return;
411         }
412
413         if ($this->saveHandler instanceof SessionHandlerProxy) {
414             session_set_save_handler($this->saveHandler->getHandler(), false);
415         } elseif ($this->saveHandler instanceof \SessionHandlerInterface) {
416             session_set_save_handler($this->saveHandler, false);
417         }
418     }
419
420     /**
421      * Load the session with attributes.
422      *
423      * After starting the session, PHP retrieves the session from whatever handlers
424      * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
425      * PHP takes the return value from the read() handler, unserializes it
426      * and populates $_SESSION with the result automatically.
427      */
428     protected function loadSession(array &$session = null)
429     {
430         if (null === $session) {
431             $session = &$_SESSION;
432         }
433
434         $bags = array_merge($this->bags, array($this->metadataBag));
435
436         foreach ($bags as $bag) {
437             $key = $bag->getStorageKey();
438             $session[$key] = isset($session[$key]) ? $session[$key] : array();
439             $bag->initialize($session[$key]);
440         }
441
442         $this->started = true;
443         $this->closed = false;
444     }
445 }