%PDF- %PDF-
| Direktori : /proc/thread-self/root/www/varak.net/losik.varak.net/vendor/nette/http/src/Http/ |
| Current File : //proc/thread-self/root/www/varak.net/losik.varak.net/vendor/nette/http/src/Http/Session.php |
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\Http;
use Nette;
/**
* Provides access to session sections as well as session settings and management methods.
*/
class Session
{
use Nette\SmartObject;
/** Default file lifetime */
private const DefaultFileLifetime = 3 * Nette\Utils\DateTime::HOUR;
private const SecurityOptions = [
'referer_check' => '', // must be disabled because PHP implementation is invalid
'use_cookies' => 1, // must be enabled to prevent Session Hijacking and Fixation
'use_only_cookies' => 1, // must be enabled to prevent Session Fixation
'use_trans_sid' => 0, // must be disabled to prevent Session Hijacking and Fixation
'use_strict_mode' => 1, // must be enabled to prevent Session Fixation
'cookie_httponly' => true, // must be enabled to prevent Session Hijacking
];
/** @var array<callable(self): void> Occurs when the session is started */
public $onStart = [];
/** @var array<callable(self): void> Occurs before the session is written to disk */
public $onBeforeWrite = [];
/** @var bool has been session ID regenerated? */
private $regenerated = false;
/** @var bool has been session started by Nette? */
private $started = false;
/** @var array default configuration */
private $options = [
'cookie_samesite' => IResponse::SameSiteLax,
'cookie_lifetime' => 0, // for a maximum of 3 hours or until the browser is closed
'gc_maxlifetime' => self::DefaultFileLifetime, // 3 hours
];
/** @var IRequest */
private $request;
/** @var IResponse */
private $response;
/** @var \SessionHandlerInterface */
private $handler;
/** @var bool */
private $readAndClose = false;
/** @var bool */
private $fileExists = true;
/** @var bool */
private $autoStart = true;
public function __construct(IRequest $request, IResponse $response)
{
$this->request = $request;
$this->response = $response;
$this->options['cookie_path'] = &$this->response->cookiePath;
$this->options['cookie_domain'] = &$this->response->cookieDomain;
$this->options['cookie_secure'] = &$this->response->cookieSecure;
}
/**
* Starts and initializes session data.
* @throws Nette\InvalidStateException
*/
public function start(): void
{
$this->doStart();
}
private function doStart($mustExists = false): void
{
if (session_status() === PHP_SESSION_ACTIVE) { // adapt an existing session
if (!$this->started) {
$this->configure(self::SecurityOptions);
$this->initialize();
}
return;
}
$this->configure(self::SecurityOptions + $this->options);
if (!session_id()) { // session is started for first time
$id = $this->request->getCookie(session_name());
$id = is_string($id) && preg_match('#^[0-9a-zA-Z,-]{22,256}$#Di', $id)
? $id
: session_create_id();
session_id($id); // causes resend of a cookie to make sure it has the right parameters
}
try {
// session_start returns false on failure only sometimes (even in PHP >= 7.1)
Nette\Utils\Callback::invokeSafe(
'session_start',
[['read_and_close' => $this->readAndClose]],
function (string $message) use (&$e): void {
$e = new Nette\InvalidStateException($message);
}
);
} catch (\Throwable $e) {
}
if ($e) {
@session_write_close(); // this is needed
throw $e;
}
if ($mustExists && $this->request->getCookie(session_name()) !== session_id()) {
// PHP regenerated the ID which means that the session did not exist and cookie was invalid
$this->destroy();
return;
}
$this->initialize();
Nette\Utils\Arrays::invoke($this->onStart, $this);
}
/** @internal */
public function autoStart(bool $forWrite): void
{
if ($this->started || (!$forWrite && !$this->exists())) {
return;
} elseif (!$this->autoStart) {
trigger_error('Cannot auto-start session because autostarting is disabled', E_USER_WARNING);
return;
}
$this->doStart(!$forWrite);
}
private function initialize(): void
{
$this->started = true;
$this->fileExists = true;
/* structure:
__NF: Data, Meta, Time
DATA: section->variable = data
META: section->variable = Timestamp
*/
$nf = &$_SESSION['__NF'];
if (!is_array($nf)) {
$nf = [];
}
// regenerate empty session
if (empty($nf['Time']) && !$this->readAndClose) {
$nf['Time'] = time();
if ($this->request->getCookie(session_name()) === session_id()) {
// ensures that the session was created with use_strict_mode (ie by Nette)
$this->regenerateId();
}
}
// expire section variables
$now = time();
foreach ($nf['META'] ?? [] as $section => $metadata) {
foreach ($metadata ?? [] as $variable => $value) {
if (!empty($value['T']) && $now > $value['T']) {
if ($variable === '') { // expire whole section
unset($nf['META'][$section], $nf['DATA'][$section]);
continue 2;
}
unset($nf['META'][$section][$variable], $nf['DATA'][$section][$variable]);
}
}
}
}
public function __destruct()
{
$this->clean();
}
/**
* Has been session started?
*/
public function isStarted(): bool
{
return $this->started && session_status() === PHP_SESSION_ACTIVE;
}
/**
* Ends the current session and store session data.
*/
public function close(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
$this->clean();
session_write_close();
$this->started = false;
}
}
/**
* Destroys all data registered to a session.
*/
public function destroy(): void
{
if (session_status() !== PHP_SESSION_ACTIVE) {
throw new Nette\InvalidStateException('Session is not started.');
}
session_destroy();
$_SESSION = null;
$this->started = false;
$this->fileExists = false;
if (!$this->response->isSent()) {
$params = session_get_cookie_params();
$this->response->deleteCookie(session_name(), $params['path'], $params['domain'], $params['secure']);
}
}
/**
* Does session exist for the current request?
*/
public function exists(): bool
{
return session_status() === PHP_SESSION_ACTIVE
|| ($this->fileExists && $this->request->getCookie($this->getName()));
}
/**
* Regenerates the session ID.
* @throws Nette\InvalidStateException
*/
public function regenerateId(): void
{
if ($this->regenerated) {
return;
}
if (session_status() === PHP_SESSION_ACTIVE) {
if (headers_sent($file, $line)) {
throw new Nette\InvalidStateException('Cannot regenerate session ID after HTTP headers have been sent' . ($file ? " (output started at $file:$line)." : '.'));
}
session_regenerate_id(true);
} else {
session_id(session_create_id());
}
$this->regenerated = true;
}
/**
* Returns the current session ID. Don't make dependencies, can be changed for each request.
*/
public function getId(): string
{
return session_id();
}
/**
* Sets the session name to a specified one.
* @return static
*/
public function setName(string $name)
{
if (!preg_match('#[^0-9.][^.]*$#DA', $name)) {
throw new Nette\InvalidArgumentException('Session name cannot contain dot.');
}
session_name($name);
return $this->setOptions([
'name' => $name,
]);
}
/**
* Gets the session name.
*/
public function getName(): string
{
return $this->options['name'] ?? session_name();
}
/********************* sections management ****************d*g**/
/**
* Returns specified session section.
* @throws Nette\InvalidArgumentException
*/
public function getSection(string $section, string $class = SessionSection::class): SessionSection
{
return new $class($this, $section);
}
/**
* Checks if a session section exist and is not empty.
*/
public function hasSection(string $section): bool
{
if ($this->exists() && !$this->started) {
$this->autoStart(false);
}
return !empty($_SESSION['__NF']['DATA'][$section]);
}
/** @return string[] */
public function getSectionNames(): array
{
if ($this->exists() && !$this->started) {
$this->autoStart(false);
}
return array_keys($_SESSION['__NF']['DATA'] ?? []);
}
/** @deprecated use getSectionNames() */
public function getIterator(): \Iterator
{
trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
return new \ArrayIterator($this->getSectionNames());
}
/**
* Cleans and minimizes meta structures.
*/
private function clean(): void
{
if (!$this->isStarted()) {
return;
}
Nette\Utils\Arrays::invoke($this->onBeforeWrite, $this);
$nf = &$_SESSION['__NF'];
foreach ($nf['META'] ?? [] as $name => $foo) {
if (empty($nf['META'][$name])) {
unset($nf['META'][$name]);
}
}
}
/********************* configuration ****************d*g**/
/**
* Sets session options.
* @return static
* @throws Nette\NotSupportedException
* @throws Nette\InvalidStateException
*/
public function setOptions(array $options)
{
$normalized = [];
$allowed = ini_get_all('session', false) + ['session.read_and_close' => 1, 'session.cookie_samesite' => 1]; // for PHP < 7.3
foreach ($options as $key => $value) {
if (!strncmp($key, 'session.', 8)) { // back compatibility
$key = substr($key, 8);
}
$normKey = strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $key)); // camelCase -> snake_case
if (!isset($allowed["session.$normKey"])) {
$hint = substr((string) Nette\Utils\Helpers::getSuggestion(array_keys($allowed), "session.$normKey"), 8);
if ($key !== $normKey) {
$hint = preg_replace_callback('#_(.)#', function ($m) { return strtoupper($m[1]); }, $hint); // snake_case -> camelCase
}
throw new Nette\InvalidStateException("Invalid session configuration option '$key'" . ($hint ? ", did you mean '$hint'?" : '.'));
}
$normalized[$normKey] = $value;
}
if (isset($normalized['read_and_close'])) {
if (session_status() === PHP_SESSION_ACTIVE) {
throw new Nette\InvalidStateException('Cannot configure "read_and_close" for already started session.');
}
$this->readAndClose = (bool) $normalized['read_and_close'];
unset($normalized['read_and_close']);
}
$this->autoStart = $normalized['auto_start'] ?? true;
unset($normalized['auto_start']);
if (session_status() === PHP_SESSION_ACTIVE) {
$this->configure($normalized);
}
$this->options = $normalized + $this->options;
return $this;
}
/**
* Returns all session options.
*/
public function getOptions(): array
{
return $this->options;
}
/**
* Configures session environment.
*/
private function configure(array $config): void
{
$special = ['cache_expire' => 1, 'cache_limiter' => 1, 'save_path' => 1, 'name' => 1];
$cookie = $origCookie = session_get_cookie_params();
foreach ($config as $key => $value) {
if ($value === null || ini_get("session.$key") == $value) { // intentionally ==
continue;
} elseif (strncmp($key, 'cookie_', 7) === 0) {
$cookie[substr($key, 7)] = $value;
} else {
if (session_status() === PHP_SESSION_ACTIVE) {
throw new Nette\InvalidStateException("Unable to set 'session.$key' to value '$value' when session has been started" . ($this->started ? '.' : ' by session.auto_start or session_start().'));
}
if (isset($special[$key])) {
("session_$key")($value);
} elseif (function_exists('ini_set')) {
ini_set("session.$key", (string) $value);
} else {
throw new Nette\NotSupportedException("Unable to set 'session.$key' to '$value' because function ini_set() is disabled.");
}
}
}
if ($cookie !== $origCookie) {
if (PHP_VERSION_ID >= 70300) {
@session_set_cookie_params($cookie); // @ may trigger warning when session is active since PHP 7.2
} else {
@session_set_cookie_params( // @ may trigger warning when session is active since PHP 7.2
$cookie['lifetime'],
$cookie['path'] . (isset($cookie['samesite']) ? '; SameSite=' . $cookie['samesite'] : ''),
$cookie['domain'],
$cookie['secure'],
$cookie['httponly']
);
}
if (session_status() === PHP_SESSION_ACTIVE) {
$this->sendCookie();
}
}
if ($this->handler) {
session_set_save_handler($this->handler);
}
}
/**
* Sets the amount of time (like '20 minutes') allowed between requests before the session will be terminated,
* null means "for a maximum of 3 hours or until the browser is closed".
* @return static
*/
public function setExpiration(?string $expire)
{
if ($expire === null) {
return $this->setOptions([
'gc_maxlifetime' => self::DefaultFileLifetime,
'cookie_lifetime' => 0,
]);
} else {
$expire = Nette\Utils\DateTime::from($expire)->format('U') - time();
return $this->setOptions([
'gc_maxlifetime' => $expire,
'cookie_lifetime' => $expire,
]);
}
}
/**
* Sets the session cookie parameters.
* @return static
*/
public function setCookieParameters(
string $path,
?string $domain = null,
?bool $secure = null,
?string $sameSite = null
) {
return $this->setOptions([
'cookie_path' => $path,
'cookie_domain' => $domain,
'cookie_secure' => $secure,
'cookie_samesite' => $sameSite,
]);
}
/** @deprecated */
public function getCookieParameters(): array
{
trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
return session_get_cookie_params();
}
/**
* Sets path of the directory used to save session data.
* @return static
*/
public function setSavePath(string $path)
{
return $this->setOptions([
'save_path' => $path,
]);
}
/**
* Sets user session handler.
* @return static
*/
public function setHandler(\SessionHandlerInterface $handler)
{
if ($this->started) {
throw new Nette\InvalidStateException('Unable to set handler when session has been started.');
}
$this->handler = $handler;
return $this;
}
/**
* Sends the session cookies.
*/
private function sendCookie(): void
{
$cookie = session_get_cookie_params();
$this->response->setCookie(
session_name(),
session_id(),
$cookie['lifetime'] ? $cookie['lifetime'] + time() : 0,
$cookie['path'],
$cookie['domain'],
$cookie['secure'],
$cookie['httponly'],
$cookie['samesite'] ?? null
);
}
}