PluginProbe
WPIDE – File Manager & Code Editor / 3.5.9
WPIDE – File Manager & Code Editor v3.5.9
3.5.9 3.5.8 3.5.7 2.0.14 2.0.15 2.0.16 2.0.2 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1 2.2 2.3 2.3.1 2.3.2 2.4.0 2.5 2.6 3.0 3.1 3.2 3.3 All 55 releases
wpide / vendor / symfony / http-foundation / Session / Storage / NativeSessionStorage.php

NativeSessionStorage.php in WPIDE – File Manager & Code Editor 3.5.9, at vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php

508 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\SessionUtils;
16 use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
17 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy;
18 use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy;
19
20 // Help opcache.preload discover always-needed symbols
21 class_exists(MetadataBag::class);
22 class_exists(StrictSessionHandler::class);
23 class_exists(SessionHandlerProxy::class);
24
25 /**
26 * This provides a base class for session attribute storage.
27 *
28 * @author Drak <drak@zikula.org>
29 */
30 class NativeSessionStorage implements SessionStorageInterface
31 {
32 /**
33 * @var SessionBagInterface[]
34 */
35 protected $bags = [];
36
37 /**
38 * @var bool
39 */
40 protected $started = false;
41
42 /**
43 * @var bool
44 */
45 protected $closed = false;
46
47 /**
48 * @var AbstractProxy|\SessionHandlerInterface
49 */
50 protected $saveHandler;
51
52 /**
53 * @var MetadataBag
54 */
55 protected $metadataBag;
56
57 /**
58 * @var string|null
59 */
60 private $emulateSameSite;
61
62 /**
63 * Depending on how you want the storage driver to behave you probably
64 * want to override this constructor entirely.
65 *
66 * List of options for $options array with their defaults.
67 *
68 * @see https://php.net/session.configuration for options
69 * but we omit 'session.' from the beginning of the keys for convenience.
70 *
71 * ("auto_start", is not supported as it tells PHP to start a session before
72 * PHP starts to execute user-land code. Setting during runtime has no effect).
73 *
74 * cache_limiter, "" (use "0" to prevent headers from being sent entirely).
75 * cache_expire, "0"
76 * cookie_domain, ""
77 * cookie_httponly, ""
78 * cookie_lifetime, "0"
79 * cookie_path, "/"
80 * cookie_secure, ""
81 * cookie_samesite, null
82 * gc_divisor, "100"
83 * gc_maxlifetime, "1440"
84 * gc_probability, "1"
85 * lazy_write, "1"
86 * name, "PHPSESSID"
87 * referer_check, ""
88 * serialize_handler, "php"
89 * use_strict_mode, "1"
90 * use_cookies, "1"
91 * use_only_cookies, "1"
92 * use_trans_sid, "0"
93 * sid_length, "32"
94 * sid_bits_per_character, "5"
95 * trans_sid_hosts, $_SERVER['HTTP_HOST']
96 * trans_sid_tags, "a=href,area=href,frame=src,form="
97 *
98 * @param AbstractProxy|\SessionHandlerInterface|null $handler
99 */
100 public function __construct(array $options = [], $handler = null, ?MetadataBag $metaBag = null)
101 {
102 if (!\extension_loaded('session')) {
103 throw new \LogicException('PHP extension "session" is required.');
104 }
105
106 $options += [
107 'cache_limiter' => '',
108 'cache_expire' => 0,
109 'use_cookies' => 1,
110 'lazy_write' => 1,
111 'use_strict_mode' => 1,
112 ];
113
114 session_register_shutdown();
115
116 $this->setMetadataBag($metaBag);
117 $this->setOptions($options);
118 $this->setSaveHandler($handler);
119 }
120
121 /**
122 * Gets the save handler instance.
123 *
124 * @return AbstractProxy|\SessionHandlerInterface
125 */
126 public function getSaveHandler()
127 {
128 return $this->saveHandler;
129 }
130
131 /**
132 * {@inheritdoc}
133 */
134 public function start()
135 {
136 if ($this->started) {
137 return true;
138 }
139
140 if (\PHP_SESSION_ACTIVE === session_status()) {
141 throw new \RuntimeException('Failed to start the session: already started by PHP.');
142 }
143
144 if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
145 throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
146 }
147
148 $sessionId = $_COOKIE[session_name()] ?? null;
149 /*
150 * Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`.
151 *
152 * ---------- Part 1
153 *
154 * The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.
155 * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
156 * Allowed values are integers such as:
157 * - 4 for range `a-f0-9`
158 * - 5 for range `a-v0-9`
159 * - 6 for range `a-zA-Z0-9,-`
160 *
161 * ---------- Part 2
162 *
163 * The part `{22,250}` is related to the PHP ini directive `session.sid_length`.
164 * See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.
165 * Allowed values are integers between 22 and 256, but we use 250 for the max.
166 *
167 * Where does the 250 come from?
168 * - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.
169 * - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.
170 *
171 * ---------- Conclusion
172 *
173 * The parts 1 and 2 prevent the warning below:
174 * `PHP Warning: SessionHandler::read(): Session ID is too long or contains illegal characters. Only the A-Z, a-z, 0-9, "-", and "," characters are allowed.`
175 *
176 * The part 2 prevents the warning below:
177 * `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).`
178 */
179 if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) {
180 // the session ID in the header is invalid, create a new one
181 session_id(session_create_id());
182 }
183
184 // ok to try and start the session
185 if (!session_start()) {
186 throw new \RuntimeException('Failed to start the session.');
187 }
188
189 if (null !== $this->emulateSameSite) {
190 $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
191 if (null !== $originalCookie) {
192 header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false);
193 }
194 }
195
196 $this->loadSession();
197
198 return true;
199 }
200
201 /**
202 * {@inheritdoc}
203 */
204 public function getId()
205 {
206 return $this->saveHandler->getId();
207 }
208
209 /**
210 * {@inheritdoc}
211 */
212 public function setId(string $id)
213 {
214 $this->saveHandler->setId($id);
215 }
216
217 /**
218 * {@inheritdoc}
219 */
220 public function getName()
221 {
222 return $this->saveHandler->getName();
223 }
224
225 /**
226 * {@inheritdoc}
227 */
228 public function setName(string $name)
229 {
230 $this->saveHandler->setName($name);
231 }
232
233 /**
234 * {@inheritdoc}
235 */
236 public function regenerate(bool $destroy = false, ?int $lifetime = null)
237 {
238 // Cannot regenerate the session ID for non-active sessions.
239 if (\PHP_SESSION_ACTIVE !== session_status()) {
240 return false;
241 }
242
243 if (headers_sent()) {
244 return false;
245 }
246
247 if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) {
248 $this->save();
249 ini_set('session.cookie_lifetime', $lifetime);
250 $this->start();
251 }
252
253 if ($destroy) {
254 $this->metadataBag->stampNew();
255 }
256
257 $isRegenerated = session_regenerate_id($destroy);
258
259 if (null !== $this->emulateSameSite) {
260 $originalCookie = SessionUtils::popSessionCookie(session_name(), session_id());
261 if (null !== $originalCookie) {
262 header(sprintf('%s; SameSite=%s', $originalCookie, $this->emulateSameSite), false);
263 }
264 }
265
266 return $isRegenerated;
267 }
268
269 /**
270 * {@inheritdoc}
271 */
272 public function save()
273 {
274 // Store a copy so we can restore the bags in case the session was not left empty
275 $session = $_SESSION;
276
277 foreach ($this->bags as $bag) {
278 if (empty($_SESSION[$key = $bag->getStorageKey()])) {
279 unset($_SESSION[$key]);
280 }
281 }
282 if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
283 unset($_SESSION[$key]);
284 }
285
286 // Register error handler to add information about the current save handler
287 $previousHandler = set_error_handler(function ($type, $msg, $file, $line) use (&$previousHandler) {
288 if (\E_WARNING === $type && str_starts_with($msg, 'session_write_close():')) {
289 $handler = $this->saveHandler instanceof SessionHandlerProxy ? $this->saveHandler->getHandler() : $this->saveHandler;
290 $msg = sprintf('session_write_close(): Failed to write session data with "%s" handler', \get_class($handler));
291 }
292
293 return $previousHandler ? $previousHandler($type, $msg, $file, $line) : false;
294 });
295
296 try {
297 session_write_close();
298 } finally {
299 restore_error_handler();
300
301 // Restore only if not empty
302 if ($_SESSION) {
303 $_SESSION = $session;
304 }
305 }
306
307 $this->closed = true;
308 $this->started = false;
309 }
310
311 /**
312 * {@inheritdoc}
313 */
314 public function clear()
315 {
316 // clear out the bags
317 foreach ($this->bags as $bag) {
318 $bag->clear();
319 }
320
321 // clear out the session
322 $_SESSION = [];
323
324 // reconnect the bags to the session
325 $this->loadSession();
326 }
327
328 /**
329 * {@inheritdoc}
330 */
331 public function registerBag(SessionBagInterface $bag)
332 {
333 if ($this->started) {
334 throw new \LogicException('Cannot register a bag when the session is already started.');
335 }
336
337 $this->bags[$bag->getName()] = $bag;
338 }
339
340 /**
341 * {@inheritdoc}
342 */
343 public function getBag(string $name)
344 {
345 if (!isset($this->bags[$name])) {
346 throw new \InvalidArgumentException(sprintf('The SessionBagInterface "%s" is not registered.', $name));
347 }
348
349 if (!$this->started && $this->saveHandler->isActive()) {
350 $this->loadSession();
351 } elseif (!$this->started) {
352 $this->start();
353 }
354
355 return $this->bags[$name];
356 }
357
358 public function setMetadataBag(?MetadataBag $metaBag = null)
359 {
360 if (null === $metaBag) {
361 $metaBag = new MetadataBag();
362 }
363
364 $this->metadataBag = $metaBag;
365 }
366
367 /**
368 * Gets the MetadataBag.
369 *
370 * @return MetadataBag
371 */
372 public function getMetadataBag()
373 {
374 return $this->metadataBag;
375 }
376
377 /**
378 * {@inheritdoc}
379 */
380 public function isStarted()
381 {
382 return $this->started;
383 }
384
385 /**
386 * Sets session.* ini variables.
387 *
388 * For convenience we omit 'session.' from the beginning of the keys.
389 * Explicitly ignores other ini keys.
390 *
391 * @param array $options Session ini directives [key => value]
392 *
393 * @see https://php.net/session.configuration
394 */
395 public function setOptions(array $options)
396 {
397 if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
398 return;
399 }
400
401 $validOptions = array_flip([
402 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly',
403 'cookie_lifetime', 'cookie_path', 'cookie_secure', 'cookie_samesite',
404 'gc_divisor', 'gc_maxlifetime', 'gc_probability',
405 'lazy_write', 'name', 'referer_check',
406 'serialize_handler', 'use_strict_mode', 'use_cookies',
407 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled',
408 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name',
409 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags',
410 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags',
411 ]);
412
413 foreach ($options as $key => $value) {
414 if (isset($validOptions[$key])) {
415 if (str_starts_with($key, 'upload_progress.')) {
416 trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. The settings prefixed with "session.upload_progress." can not be changed at runtime.', $key);
417 continue;
418 }
419 if ('url_rewriter.tags' === $key) {
420 trigger_deprecation('symfony/http-foundation', '5.4', 'Support for the "%s" session option is deprecated. Use "trans_sid_tags" instead.', $key);
421 }
422 if ('cookie_samesite' === $key && \PHP_VERSION_ID < 70300) {
423 // PHP < 7.3 does not support same_site cookies. We will emulate it in
424 // the start() method instead.
425 $this->emulateSameSite = $value;
426 continue;
427 }
428 if ('cookie_secure' === $key && 'auto' === $value) {
429 continue;
430 }
431 ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value);
432 }
433 }
434 }
435
436 /**
437 * Registers session save handler as a PHP session handler.
438 *
439 * To use internal PHP session save handlers, override this method using ini_set with
440 * session.save_handler and session.save_path e.g.
441 *
442 * ini_set('session.save_handler', 'files');
443 * ini_set('session.save_path', '/tmp');
444 *
445 * or pass in a \SessionHandler instance which configures session.save_handler in the
446 * constructor, for a template see NativeFileSessionHandler.
447 *
448 * @see https://php.net/session-set-save-handler
449 * @see https://php.net/sessionhandlerinterface
450 * @see https://php.net/sessionhandler
451 *
452 * @param AbstractProxy|\SessionHandlerInterface|null $saveHandler
453 *
454 * @throws \InvalidArgumentException
455 */
456 public function setSaveHandler($saveHandler = null)
457 {
458 if (!$saveHandler instanceof AbstractProxy
459 && !$saveHandler instanceof \SessionHandlerInterface
460 && null !== $saveHandler
461 ) {
462 throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
463 }
464
465 // Wrap $saveHandler in proxy and prevent double wrapping of proxy
466 if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) {
467 $saveHandler = new SessionHandlerProxy($saveHandler);
468 } elseif (!$saveHandler instanceof AbstractProxy) {
469 $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler()));
470 }
471 $this->saveHandler = $saveHandler;
472
473 if (headers_sent() || \PHP_SESSION_ACTIVE === session_status()) {
474 return;
475 }
476
477 if ($this->saveHandler instanceof SessionHandlerProxy) {
478 session_set_save_handler($this->saveHandler, false);
479 }
480 }
481
482 /**
483 * Load the session with attributes.
484 *
485 * After starting the session, PHP retrieves the session from whatever handlers
486 * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()).
487 * PHP takes the return value from the read() handler, unserializes it
488 * and populates $_SESSION with the result automatically.
489 */
490 protected function loadSession(?array &$session = null)
491 {
492 if (null === $session) {
493 $session = &$_SESSION;
494 }
495
496 $bags = array_merge($this->bags, [$this->metadataBag]);
497
498 foreach ($bags as $bag) {
499 $key = $bag->getStorageKey();
500 $session[$key] = isset($session[$key]) && \is_array($session[$key]) ? $session[$key] : [];
501 $bag->initialize($session[$key]);
502 }
503
504 $this->started = true;
505 $this->closed = false;
506 }
507 }
508