Aggiornato Composer

This commit is contained in:
Paolo A
2024-05-17 12:24:19 +00:00
parent 4ac62108b5
commit ec201d75b2
2238 changed files with 38684 additions and 59785 deletions

View File

@@ -34,6 +34,7 @@ class BinaryFileResponse extends Response
protected $offset = 0;
protected $maxlen = -1;
protected $deleteFileAfterSend = false;
protected $chunkSize = 16 * 1024;
/**
* @param \SplFileInfo|string $file The file to stream
@@ -44,7 +45,7 @@ class BinaryFileResponse extends Response
* @param bool $autoEtag Whether the ETag header should be automatically set
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
*/
public function __construct($file, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
public function __construct($file, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
{
parent::__construct(null, $status, $headers);
@@ -68,7 +69,7 @@ class BinaryFileResponse extends Response
*
* @deprecated since Symfony 5.2, use __construct() instead.
*/
public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
{
trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
@@ -84,7 +85,7 @@ class BinaryFileResponse extends Response
*
* @throws FileException
*/
public function setFile($file, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
public function setFile($file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
{
if (!$file instanceof File) {
if ($file instanceof \SplFileInfo) {
@@ -125,6 +126,22 @@ class BinaryFileResponse extends Response
return $this->file;
}
/**
* Sets the response stream chunk size.
*
* @return $this
*/
public function setChunkSize(int $chunkSize): self
{
if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
}
$this->chunkSize = $chunkSize;
return $this;
}
/**
* Automatically sets the Last-Modified header according the file modification date.
*
@@ -189,15 +206,19 @@ class BinaryFileResponse extends Response
*/
public function prepare(Request $request)
{
if ($this->isInformational() || $this->isEmpty()) {
parent::prepare($request);
$this->maxlen = 0;
return $this;
}
if (!$this->headers->has('Content-Type')) {
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
}
if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) {
$this->setProtocolVersion('1.1');
}
$this->ensureIEOverSSLCompatibility($request);
parent::prepare($request);
$this->offset = 0;
$this->maxlen = -1;
@@ -205,6 +226,7 @@ class BinaryFileResponse extends Response
if (false === $fileSize = $this->file->getSize()) {
return $this;
}
$this->headers->remove('Transfer-Encoding');
$this->headers->set('Content-Length', $fileSize);
if (!$this->headers->has('Accept-Ranges')) {
@@ -245,7 +267,7 @@ class BinaryFileResponse extends Response
$range = $request->headers->get('Range');
if (str_starts_with($range, 'bytes=')) {
[$start, $end] = explode('-', substr($range, 6), 2) + [0];
[$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0];
$end = ('' === $end) ? $fileSize - 1 : (int) $end;
@@ -274,6 +296,10 @@ class BinaryFileResponse extends Response
}
}
if ($request->isMethod('HEAD')) {
$this->maxlen = 0;
}
return $this;
}
@@ -295,24 +321,49 @@ class BinaryFileResponse extends Response
*/
public function sendContent()
{
if (!$this->isSuccessful()) {
return parent::sendContent();
}
try {
if (!$this->isSuccessful()) {
return parent::sendContent();
}
if (0 === $this->maxlen) {
return $this;
}
if (0 === $this->maxlen) {
return $this;
}
$out = fopen('php://output', 'w');
$file = fopen($this->file->getPathname(), 'r');
$out = fopen('php://output', 'w');
$file = fopen($this->file->getPathname(), 'r');
stream_copy_to_stream($file, $out, $this->maxlen, $this->offset);
ignore_user_abort(true);
fclose($out);
fclose($file);
if (0 !== $this->offset) {
fseek($file, $this->offset);
}
if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
unlink($this->file->getPathname());
$length = $this->maxlen;
while ($length && !feof($file)) {
$read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length;
if (false === $data = fread($file, $read)) {
break;
}
while ('' !== $data) {
$read = fwrite($out, $data);
if (false === $read || connection_aborted()) {
break 2;
}
if (0 < $length) {
$length -= $read;
}
$data = substr($data, $read);
}
}
fclose($out);
fclose($file);
} finally {
if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
unlink($this->file->getPathname());
}
}
return $this;

View File

@@ -71,7 +71,7 @@ class Cookie
return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
}
public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self
public static function create(string $name, ?string $value = null, $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self
{
return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite);
}
@@ -80,7 +80,7 @@ class Cookie
* @param string $name The name of the cookie
* @param string|null $value The value of the cookie
* @param int|string|\DateTimeInterface $expire The time the cookie expires
* @param string $path The path on the server in which the cookie will be available on
* @param string|null $path The path on the server in which the cookie will be available on
* @param string|null $domain The domain that the cookie is available to
* @param bool|null $secure Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
@@ -89,7 +89,7 @@ class Cookie
*
* @throws \InvalidArgumentException
*/
public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
public function __construct(string $name, ?string $value = null, $expire = 0, ?string $path = '/', ?string $domain = null, ?bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
{
// from PHP source code
if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\HttpFoundation\Exception;
/**
* Raised when a session does not exists. This happens in the following cases:
* Raised when a session does not exist. This happens in the following cases:
* - the session is not enabled
* - attempt to read a session outside a request context (ie. cli script).
*
@@ -20,7 +20,7 @@ namespace Symfony\Component\HttpFoundation\Exception;
*/
class SessionNotFoundException extends \LogicException implements RequestExceptionInterface
{
public function __construct(string $message = 'There is currently no session available.', int $code = 0, \Throwable $previous = null)
public function __construct(string $message = 'There is currently no session available.', int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}

View File

@@ -88,7 +88,7 @@ class File extends \SplFileInfo
*
* @throws FileException if the target file could not be created
*/
public function move(string $directory, string $name = null)
public function move(string $directory, ?string $name = null)
{
$target = $this->getTargetFile($directory, $name);
@@ -121,7 +121,7 @@ class File extends \SplFileInfo
/**
* @return self
*/
protected function getTargetFile(string $directory, string $name = null)
protected function getTargetFile(string $directory, ?string $name = null)
{
if (!is_dir($directory)) {
if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {

View File

@@ -60,7 +60,7 @@ class UploadedFile extends File
* @throws FileException If file_uploads is disabled
* @throws FileNotFoundException If the file does not exist
*/
public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false)
public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null, bool $test = false)
{
$this->originalName = $this->getName($originalName);
$this->mimeType = $mimeType ?: 'application/octet-stream';
@@ -74,7 +74,7 @@ class UploadedFile extends File
* Returns the original file name.
*
* It is extracted from the request from which the file has been uploaded.
* Then it should not be considered as a safe value.
* This should not be considered as a safe value to use for a file name on your servers.
*
* @return string
*/
@@ -87,7 +87,7 @@ class UploadedFile extends File
* Returns the original file extension.
*
* It is extracted from the original file name that was uploaded.
* Then it should not be considered as a safe value.
* This should not be considered as a safe value to use for a file name on your servers.
*
* @return string
*/
@@ -172,7 +172,7 @@ class UploadedFile extends File
*
* @throws FileException if, for any reason, the file could not have been moved
*/
public function move(string $directory, string $name = null)
public function move(string $directory, ?string $name = null)
{
if ($this->isValid()) {
if ($this->test) {
@@ -223,8 +223,8 @@ class UploadedFile extends File
*/
public static function getMaxFilesize()
{
$sizePostMax = self::parseFilesize(ini_get('post_max_size'));
$sizeUploadMax = self::parseFilesize(ini_get('upload_max_filesize'));
$sizePostMax = self::parseFilesize(\ini_get('post_max_size'));
$sizeUploadMax = self::parseFilesize(\ini_get('upload_max_filesize'));
return min($sizePostMax ?: \PHP_INT_MAX, $sizeUploadMax ?: \PHP_INT_MAX);
}
@@ -253,11 +253,11 @@ class UploadedFile extends File
switch (substr($size, -1)) {
case 't': $max *= 1024;
// no break
// no break
case 'g': $max *= 1024;
// no break
// no break
case 'm': $max *= 1024;
// no break
// no break
case 'k': $max *= 1024;
}

View File

@@ -67,7 +67,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*
* @return array<string, array<int, string|null>>|array<int, string|null>
*/
public function all(string $key = null)
public function all(?string $key = null)
{
if (null !== $key) {
return $this->headers[strtr($key, self::UPPER, self::LOWER)] ?? [];
@@ -110,7 +110,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*
* @return string|null
*/
public function get(string $key, string $default = null)
public function get(string $key, ?string $default = null)
{
$headers = $this->all($key);
@@ -197,7 +197,7 @@ class HeaderBag implements \IteratorAggregate, \Countable
*
* @throws \RuntimeException When the HTTP header is not parseable
*/
public function getDate(string $key, \DateTime $default = null)
public function getDate(string $key, ?\DateTime $default = null)
{
if (null === $value = $this->get($key)) {
return $default;

View File

@@ -33,17 +33,21 @@ class HeaderUtils
*
* Example:
*
* HeaderUtils::split("da, en-gb;q=0.8", ",;")
* HeaderUtils::split('da, en-gb;q=0.8', ',;')
* // => ['da'], ['en-gb', 'q=0.8']]
*
* @param string $separators List of characters to split on, ordered by
* precedence, e.g. ",", ";=", or ",;="
* precedence, e.g. ',', ';=', or ',;='
*
* @return array Nested array with as many levels as there are characters in
* $separators
*/
public static function split(string $header, string $separators): array
{
if ('' === $separators) {
throw new \InvalidArgumentException('At least one separator must be specified.');
}
$quotedSeparators = preg_quote($separators, '/');
preg_match_all('
@@ -77,8 +81,8 @@ class HeaderUtils
*
* Example:
*
* HeaderUtils::combine([["foo", "abc"], ["bar"]])
* // => ["foo" => "abc", "bar" => true]
* HeaderUtils::combine([['foo', 'abc'], ['bar']])
* // => ['foo' => 'abc', 'bar' => true]
*/
public static function combine(array $parts): array
{
@@ -95,13 +99,13 @@ class HeaderUtils
/**
* Joins an associative array into a string for use in an HTTP header.
*
* The key and value of each entry are joined with "=", and all entries
* The key and value of each entry are joined with '=', and all entries
* are joined with the specified separator and an additional space (for
* readability). Values are quoted if necessary.
*
* Example:
*
* HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",")
* HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',')
* // => 'foo=abc, bar, baz="a b c"'
*/
public static function toString(array $assoc, string $separator): string
@@ -138,7 +142,7 @@ class HeaderUtils
* Decodes a quoted string.
*
* If passed an unquoted string that matches the "token" construct (as
* defined in the HTTP specification), it is passed through verbatimly.
* defined in the HTTP specification), it is passed through verbatim.
*/
public static function unquote(string $s): string
{
@@ -252,40 +256,37 @@ class HeaderUtils
private static function groupParts(array $matches, string $separators, bool $first = true): array
{
$separator = $separators[0];
$partSeparators = substr($separators, 1);
$separators = substr($separators, 1) ?: '';
$i = 0;
if ('' === $separators && !$first) {
$parts = [''];
foreach ($matches as $match) {
if (!$i && isset($match['separator'])) {
$i = 1;
$parts[1] = '';
} else {
$parts[$i] .= self::unquote($match[0]);
}
}
return $parts;
}
$parts = [];
$partMatches = [];
$previousMatchWasSeparator = false;
foreach ($matches as $match) {
if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) {
$previousMatchWasSeparator = true;
$partMatches[$i][] = $match;
} elseif (isset($match['separator']) && $match['separator'] === $separator) {
$previousMatchWasSeparator = true;
if (($match['separator'] ?? null) === $separator) {
++$i;
} else {
$previousMatchWasSeparator = false;
$partMatches[$i][] = $match;
}
}
$parts = [];
if ($partSeparators) {
foreach ($partMatches as $matches) {
$parts[] = self::groupParts($matches, $partSeparators, false);
}
} else {
foreach ($partMatches as $matches) {
$parts[] = self::unquote($matches[0][0]);
}
if (!$first && 2 < \count($parts)) {
$parts = [
$parts[0],
implode($separator, \array_slice($parts, 1)),
];
}
foreach ($partMatches as $matches) {
$parts[] = '' === $separators ? self::unquote($matches[0][0]) : self::groupParts($matches, $separators, false);
}
return $parts;

View File

@@ -29,14 +29,14 @@ final class InputBag extends ParameterBag
*/
public function get(string $key, $default = null)
{
if (null !== $default && !is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) {
if (null !== $default && !\is_scalar($default) && !(\is_object($default) && method_exists($default, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Passing a non-scalar value as 2nd argument to "%s()" is deprecated, pass a scalar or null instead.', __METHOD__);
}
$value = parent::get($key, $this);
if (null !== $value && $this !== $value && !is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-string value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__);
if (null !== $value && $this !== $value && !\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Retrieving a non-scalar value from "%s()" is deprecated, and will throw a "%s" exception in Symfony 6.0, use "%s::all($key)" instead.', __METHOD__, BadRequestException::class, __CLASS__);
}
return $this === $value ? $default : $value;
@@ -45,7 +45,7 @@ final class InputBag extends ParameterBag
/**
* {@inheritdoc}
*/
public function all(string $key = null): array
public function all(?string $key = null): array
{
return parent::all($key);
}
@@ -76,7 +76,7 @@ final class InputBag extends ParameterBag
*/
public function set(string $key, $value)
{
if (null !== $value && !is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) {
if (null !== $value && !\is_scalar($value) && !\is_array($value) && !method_exists($value, '__toString')) {
trigger_deprecation('symfony/http-foundation', '5.1', 'Passing "%s" as a 2nd Argument to "%s()" is deprecated, pass a scalar, array, or null instead.', get_debug_type($value), __METHOD__);
}

View File

@@ -73,7 +73,7 @@ class IpUtils
return false;
}
$cacheKey = $requestIp.'-'.$ip;
$cacheKey = $requestIp.'-'.$ip.'-v4';
if (isset(self::$checkedIps[$cacheKey])) {
return self::$checkedIps[$cacheKey];
}
@@ -86,7 +86,7 @@ class IpUtils
[$address, $netmask] = explode('/', $ip, 2);
if ('0' === $netmask) {
return self::$checkedIps[$cacheKey] = filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4);
return self::$checkedIps[$cacheKey] = false !== filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4);
}
if ($netmask < 0 || $netmask > 32) {
@@ -126,7 +126,7 @@ class IpUtils
return false;
}
$cacheKey = $requestIp.'-'.$ip;
$cacheKey = $requestIp.'-'.$ip.'-v6';
if (isset(self::$checkedIps[$cacheKey])) {
return self::$checkedIps[$cacheKey];
}
@@ -135,9 +135,18 @@ class IpUtils
throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".');
}
// Check to see if we were given a IP4 $requestIp or $ip by mistake
if (!filter_var($requestIp, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return self::$checkedIps[$cacheKey] = false;
}
if (str_contains($ip, '/')) {
[$address, $netmask] = explode('/', $ip, 2);
if (!filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return self::$checkedIps[$cacheKey] = false;
}
if ('0' === $netmask) {
return (bool) unpack('n*', @inet_pton($address));
}
@@ -146,6 +155,10 @@ class IpUtils
return self::$checkedIps[$cacheKey] = false;
}
} else {
if (!filter_var($ip, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
return self::$checkedIps[$cacheKey] = false;
}
$address = $ip;
$netmask = 128;
}

View File

@@ -105,7 +105,7 @@ class JsonResponse extends Response
*
* @throws \InvalidArgumentException When the callback name is not valid
*/
public function setCallback(string $callback = null)
public function setCallback(?string $callback = null)
{
if (null !== $callback) {
// partially taken from https://geekality.net/2011/08/03/valid-javascript-identifier/

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2022 Fabien Potencier
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -39,7 +39,7 @@ class ParameterBag implements \IteratorAggregate, \Countable
*
* @return array
*/
public function all(/*string $key = null*/)
public function all(/* ?string $key = null */)
{
$key = \func_num_args() > 0 ? func_get_arg(0) : null;

View File

@@ -35,9 +35,7 @@ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface
foreach ($limiters as $limiter) {
$rateLimit = $limiter->consume(1);
if (null === $minimalRateLimit || $rateLimit->getRemainingTokens() < $minimalRateLimit->getRemainingTokens()) {
$minimalRateLimit = $rateLimit;
}
$minimalRateLimit = $minimalRateLimit ? self::getMinimalRateLimit($minimalRateLimit, $rateLimit) : $rateLimit;
}
return $minimalRateLimit;
@@ -54,4 +52,20 @@ abstract class AbstractRequestRateLimiter implements RequestRateLimiterInterface
* @return LimiterInterface[] a set of limiters using keys extracted from the request
*/
abstract protected function getLimiters(Request $request): array;
private static function getMinimalRateLimit(RateLimit $first, RateLimit $second): RateLimit
{
if ($first->isAccepted() !== $second->isAccepted()) {
return $first->isAccepted() ? $second : $first;
}
$firstRemainingTokens = $first->getRemainingTokens();
$secondRemainingTokens = $second->getRemainingTokens();
if ($firstRemainingTokens === $secondRemainingTokens) {
return $first->getRetryAfter() < $second->getRetryAfter() ? $second : $first;
}
return $firstRemainingTokens > $secondRemainingTokens ? $second : $first;
}
}

View File

@@ -103,6 +103,7 @@ class RedirectResponse extends Response
</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
$this->headers->set('Location', $url);
$this->headers->set('Content-Type', 'text/html; charset=utf-8');
return $this;
}

View File

@@ -191,7 +191,7 @@ class Request
protected $session;
/**
* @var string
* @var string|null
*/
protected $locale;
@@ -246,6 +246,9 @@ class Request
self::HEADER_X_FORWARDED_PREFIX => 'X_FORWARDED_PREFIX',
];
/** @var bool */
private $isIisRewrite = false;
/**
* @param array $query The GET parameters
* @param array $request The POST parameters
@@ -439,16 +442,16 @@ class Request
/**
* Clones a request and overrides some of its parameters.
*
* @param array $query The GET parameters
* @param array $request The POST parameters
* @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array $cookies The COOKIE parameters
* @param array $files The FILES parameters
* @param array $server The SERVER parameters
* @param array|null $query The GET parameters
* @param array|null $request The POST parameters
* @param array|null $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
* @param array|null $cookies The COOKIE parameters
* @param array|null $files The FILES parameters
* @param array|null $server The SERVER parameters
*
* @return static
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
public function duplicate(?array $query = null, ?array $request = null, ?array $attributes = null, ?array $cookies = null, ?array $files = null, ?array $server = null)
{
$dup = clone $this;
if (null !== $query) {
@@ -522,10 +525,10 @@ class Request
$cookies = [];
foreach ($this->cookies as $k => $v) {
$cookies[] = $k.'='.$v;
$cookies[] = \is_array($v) ? http_build_query([$k => $v], '', '; ', \PHP_QUERY_RFC3986) : "$k=$v";
}
if (!empty($cookies)) {
if ($cookies) {
$cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
}
@@ -562,7 +565,7 @@ class Request
$request = ['g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE];
$requestOrder = ini_get('request_order') ?: ini_get('variables_order');
$requestOrder = \ini_get('request_order') ?: \ini_get('variables_order');
$requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
$_REQUEST = [[]];
@@ -1452,7 +1455,7 @@ class Request
*/
public function getLocale()
{
return null === $this->locale ? $this->defaultLocale : $this->locale;
return $this->locale ?? $this->defaultLocale;
}
/**
@@ -1573,9 +1576,9 @@ class Request
/**
* Gets the request body decoded as array, typically from a JSON payload.
*
* @throws JsonException When the body cannot be decoded to an array
*
* @return array
*
* @throws JsonException When the body cannot be decoded to an array
*/
public function toArray()
{
@@ -1648,7 +1651,7 @@ class Request
*
* @return string|null
*/
public function getPreferredLanguage(array $locales = null)
public function getPreferredLanguage(?array $locales = null)
{
$preferredLanguages = $this->getLanguages();
@@ -1689,7 +1692,8 @@ class Request
$languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
$this->languages = [];
foreach ($languages as $lang => $acceptHeaderItem) {
foreach ($languages as $acceptHeaderItem) {
$lang = $acceptHeaderItem->getValue();
if (str_contains($lang, '-')) {
$codes = explode('-', $lang);
if ('i' === $codes[0]) {
@@ -1727,7 +1731,7 @@ class Request
return $this->charsets;
}
return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
return $this->charsets = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()));
}
/**
@@ -1741,7 +1745,7 @@ class Request
return $this->encodings;
}
return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
return $this->encodings = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()));
}
/**
@@ -1755,7 +1759,7 @@ class Request
return $this->acceptableContentTypes;
}
return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
return $this->acceptableContentTypes = array_map('strval', array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()));
}
/**
@@ -1804,11 +1808,10 @@ class Request
{
$requestUri = '';
if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
if ($this->isIisRewrite() && '' != $this->server->get('UNENCODED_URL')) {
// IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
$requestUri = $this->server->get('UNENCODED_URL');
$this->server->remove('UNENCODED_URL');
$this->server->remove('IIS_WasUrlRewritten');
} elseif ($this->server->has('REQUEST_URI')) {
$requestUri = $this->server->get('REQUEST_URI');
@@ -2011,7 +2014,13 @@ class Request
*/
private function getUrlencodedPrefix(string $string, string $prefix): ?string
{
if (!str_starts_with(rawurldecode($string), $prefix)) {
if ($this->isIisRewrite()) {
// ISS with UrlRewriteModule might report SCRIPT_NAME/PHP_SELF with wrong case
// see https://github.com/php/php-src/issues/11981
if (0 !== stripos(rawurldecode($string), $prefix)) {
return null;
}
} elseif (!str_starts_with(rawurldecode($string), $prefix)) {
return null;
}
@@ -2052,7 +2061,7 @@ class Request
return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR', ''), self::$trustedProxies);
}
private function getTrustedValues(int $type, string $ip = null): array
private function getTrustedValues(int $type, ?string $ip = null): array
{
$clientValues = [];
$forwardedValues = [];
@@ -2144,4 +2153,20 @@ class Request
// Now the IP chain contains only untrusted proxies and the client IP
return $clientIps ? array_reverse($clientIps) : [$firstTrustedIp];
}
/**
* Is this IIS with UrlRewriteModule?
*
* This method consumes, caches and removed the IIS_WasUrlRewritten env var,
* so we don't inherit it to sub-requests.
*/
private function isIisRewrite(): bool
{
if (1 === $this->server->getInt('IIS_WasUrlRewritten')) {
$this->isIisRewrite = true;
$this->server->remove('IIS_WasUrlRewritten');
}
return $this->isIisRewrite;
}
}

View File

@@ -58,7 +58,7 @@ class RequestMatcher implements RequestMatcherInterface
* @param string|string[]|null $ips
* @param string|string[]|null $schemes
*/
public function __construct(string $path = null, string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, int $port = null)
public function __construct(?string $path = null, ?string $host = null, $methods = null, $ips = null, array $attributes = [], $schemes = null, ?int $port = null)
{
$this->matchPath($path);
$this->matchHost($host);
@@ -91,7 +91,7 @@ class RequestMatcher implements RequestMatcherInterface
}
/**
* Adds a check for the the URL port.
* Adds a check for the URL port.
*
* @param int|null $port The port number to connect to
*/

View File

@@ -72,7 +72,7 @@ class Response
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725
public const HTTP_INTERNAL_SERVER_ERROR = 500;
public const HTTP_NOT_IMPLEMENTED = 501;
public const HTTP_BAD_GATEWAY = 502;
@@ -298,7 +298,7 @@ class Response
$charset = $this->charset ?: 'UTF-8';
if (!$headers->has('Content-Type')) {
$headers->set('Content-Type', 'text/html; charset='.$charset);
} elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
} elseif (0 === stripos($headers->get('Content-Type') ?? '', 'text/') && false === stripos($headers->get('Content-Type') ?? '', 'charset')) {
// add the charset
$headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
}
@@ -399,6 +399,7 @@ class Response
litespeed_finish_request();
} elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
static::closeOutputBuffers(0, true);
flush();
}
return $this;
@@ -462,7 +463,7 @@ class Response
*
* @final
*/
public function setStatusCode(int $code, string $text = null): object
public function setStatusCode(int $code, ?string $text = null): object
{
$this->statusCode = $code;
if ($this->isInvalid()) {
@@ -736,7 +737,7 @@ class Response
*
* @final
*/
public function setExpires(\DateTimeInterface $date = null): object
public function setExpires(?\DateTimeInterface $date = null): object
{
if (null === $date) {
$this->headers->remove('Expires');
@@ -773,8 +774,10 @@ class Response
return (int) $this->headers->getCacheControlDirective('max-age');
}
if (null !== $this->getExpires()) {
return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U');
if (null !== $expires = $this->getExpires()) {
$maxAge = (int) $expires->format('U') - (int) $this->getDate()->format('U');
return max($maxAge, 0);
}
return null;
@@ -818,7 +821,7 @@ class Response
*
* It returns null when no freshness information is present in the response.
*
* When the responses TTL is <= 0, the response may not be served from cache without first
* When the response's TTL is 0, the response may not be served from cache without first
* revalidating with the origin.
*
* @final
@@ -827,7 +830,7 @@ class Response
{
$maxAge = $this->getMaxAge();
return null !== $maxAge ? $maxAge - $this->getAge() : null;
return null !== $maxAge ? max($maxAge - $this->getAge(), 0) : null;
}
/**
@@ -883,7 +886,7 @@ class Response
*
* @final
*/
public function setLastModified(\DateTimeInterface $date = null): object
public function setLastModified(?\DateTimeInterface $date = null): object
{
if (null === $date) {
$this->headers->remove('Last-Modified');
@@ -921,7 +924,7 @@ class Response
*
* @final
*/
public function setEtag(string $etag = null, bool $weak = false): object
public function setEtag(?string $etag = null, bool $weak = false): object
{
if (null === $etag) {
$this->headers->remove('Etag');
@@ -1214,7 +1217,7 @@ class Response
*
* @final
*/
public function isRedirect(string $location = null): bool
public function isRedirect(?string $location = null): bool
{
return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location'));
}

View File

@@ -88,7 +88,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* {@inheritdoc}
*/
public function all(string $key = null)
public function all(?string $key = null)
{
$headers = parent::all();
@@ -186,7 +186,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* Removes a cookie from the array, but does not unset it in the browser.
*/
public function removeCookie(string $name, ?string $path = '/', string $domain = null)
public function removeCookie(string $name, ?string $path = '/', ?string $domain = null)
{
if (null === $path) {
$path = '/';
@@ -239,7 +239,7 @@ class ResponseHeaderBag extends HeaderBag
/**
* Clears a cookie in the browser.
*/
public function clearCookie(string $name, ?string $path = '/', string $domain = null, bool $secure = false, bool $httpOnly = true, string $sameSite = null)
public function clearCookie(string $name, ?string $path = '/', ?string $domain = null, bool $secure = false, bool $httpOnly = true, ?string $sameSite = null)
{
$this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly, false, $sameSite));
}

View File

@@ -31,7 +31,7 @@ class ServerBag extends ParameterBag
foreach ($this->parameters as $key => $value) {
if (str_starts_with($key, 'HTTP_')) {
$headers[substr($key, 5)] = $value;
} elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) {
} elseif (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true) && '' !== $value) {
$headers[$key] = $value;
}
}
@@ -51,7 +51,7 @@ class ServerBag extends ParameterBag
* RewriteCond %{HTTP:Authorization} .+
* RewriteRule ^ - [E=HTTP_AUTHORIZATION:%0]
* RewriteCond %{REQUEST_FILENAME} !-f
* RewriteRule ^(.*)$ app.php [QSA,L]
* RewriteRule ^(.*)$ index.php [QSA,L]
*/
$authorizationHeader = null;

View File

@@ -39,7 +39,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
private $usageIndex = 0;
private $usageReporter;
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null)
public function __construct(?SessionStorageInterface $storage = null, ?AttributeBagInterface $attributes = null, ?FlashBagInterface $flashes = null, ?callable $usageReporter = null)
{
$this->storage = $storage ?? new NativeSessionStorage();
$this->usageReporter = $usageReporter;
@@ -175,7 +175,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function invalidate(int $lifetime = null)
public function invalidate(?int $lifetime = null)
{
$this->storage->clear();
@@ -185,7 +185,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
/**
* {@inheritdoc}
*/
public function migrate(bool $destroy = false, int $lifetime = null)
public function migrate(bool $destroy = false, ?int $lifetime = null)
{
return $this->storage->regenerate($destroy, $lifetime);
}

View File

@@ -26,7 +26,7 @@ class SessionFactory implements SessionFactoryInterface
private $storageFactory;
private $usageReporter;
public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null)
public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, ?callable $usageReporter = null)
{
$this->requestStack = $requestStack;
$this->storageFactory = $storageFactory;

View File

@@ -59,28 +59,28 @@ interface SessionInterface
* Clears all session attributes and flashes and regenerates the
* session and deletes the old session from persistence.
*
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
* @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool
*/
public function invalidate(int $lifetime = null);
public function invalidate(?int $lifetime = null);
/**
* Migrates the current session to a new session id while maintaining all
* session attributes.
*
* @param bool $destroy Whether to delete the old session or leave it to garbage collection
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
* @param bool $destroy Whether to delete the old session or leave it to garbage collection
* @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool
*/
public function migrate(bool $destroy = false, int $lifetime = null);
public function migrate(bool $destroy = false, ?int $lifetime = null);
/**
* Force the session to be saved and closed.

View File

@@ -35,8 +35,8 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
public function open($savePath, $sessionName)
{
$this->sessionName = $sessionName;
if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
if (!headers_sent() && !\ini_get('session.cache_limiter') && '0' !== \ini_get('session.cache_limiter')) {
header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) \ini_get('session.cache_expire')));
}
return true;
@@ -126,7 +126,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
#[\ReturnTypeWillChange]
public function destroy($sessionId)
{
if (!headers_sent() && filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) {
if (!headers_sent() && filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN)) {
if (!$this->sessionName) {
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', static::class));
}
@@ -141,7 +141,7 @@ abstract class AbstractSessionHandler implements \SessionHandlerInterface, \Sess
*/
if (null === $cookie || isset($_COOKIE[$this->sessionName])) {
if (\PHP_VERSION_ID < 70300) {
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN));
setcookie($this->sessionName, '', 0, \ini_get('session.cookie_path'), \ini_get('session.cookie_domain'), filter_var(\ini_get('session.cookie_secure'), \FILTER_VALIDATE_BOOLEAN), filter_var(\ini_get('session.cookie_httponly'), \FILTER_VALIDATE_BOOLEAN));
} else {
$params = session_get_cookie_params();
unset($params['lifetime']);

View File

@@ -77,7 +77,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
$this->memcached->touch($this->prefix.$sessionId, time() + (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')));
$this->memcached->touch($this->prefix.$sessionId, $this->getCompatibleTtl());
return true;
}
@@ -87,7 +87,20 @@ class MemcachedSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data)
{
return $this->memcached->set($this->prefix.$sessionId, $data, time() + (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')));
return $this->memcached->set($this->prefix.$sessionId, $data, $this->getCompatibleTtl());
}
private function getCompatibleTtl(): int
{
$ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'));
// If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time.
// We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct.
if ($ttl > 60 * 60 * 24 * 30) {
$ttl += time();
}
return $ttl;
}
/**

View File

@@ -121,7 +121,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data)
{
$expiry = new UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000);
$expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$fields = [
$this->options['time_field'] => new UTCDateTime(),
@@ -144,7 +144,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
$expiry = new UTCDateTime((time() + (int) ini_get('session.gc_maxlifetime')) * 1000);
$expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
$this->getCollection()->updateOne(
[$this->options['id_field'] => $sessionId],

View File

@@ -19,19 +19,19 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
class NativeFileSessionHandler extends \SessionHandler
{
/**
* @param string $savePath Path of directory to save session files
* Default null will leave setting as defined by PHP.
* '/path', 'N;/path', or 'N;octal-mode;/path
* @param string|null $savePath Path of directory to save session files
* Default null will leave setting as defined by PHP.
* '/path', 'N;/path', or 'N;octal-mode;/path
*
* @see https://php.net/session.configuration#ini.session.save-path for further details.
*
* @throws \InvalidArgumentException On invalid $savePath
* @throws \RuntimeException When failing to create the save directory
*/
public function __construct(string $savePath = null)
public function __construct(?string $savePath = null)
{
if (null === $savePath) {
$savePath = ini_get('session.save_path');
$savePath = \ini_get('session.save_path');
}
$baseDir = $savePath;
@@ -49,7 +49,11 @@ class NativeFileSessionHandler extends \SessionHandler
throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s".', $baseDir));
}
ini_set('session.save_path', $savePath);
ini_set('session.save_handler', 'files');
if ($savePath !== \ini_get('session.save_path')) {
ini_set('session.save_path', $savePath);
}
if ('files' !== \ini_get('session.save_handler')) {
ini_set('session.save_handler', 'files');
}
}
}

View File

@@ -112,16 +112,16 @@ class PdoSessionHandler extends AbstractSessionHandler
/**
* Username when lazy-connect.
*
* @var string
* @var string|null
*/
private $username = '';
private $username = null;
/**
* Password when lazy-connect.
*
* @var string
* @var string|null
*/
private $password = '';
private $password = null;
/**
* Connection options when lazy-connect.
@@ -344,7 +344,7 @@ class PdoSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data)
{
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
$maxlifetime = (int) \ini_get('session.gc_maxlifetime');
try {
// We use a single MERGE SQL query when supported by the database.
@@ -391,14 +391,14 @@ class PdoSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
$expiry = time() + (int) ini_get('session.gc_maxlifetime');
$expiry = time() + (int) \ini_get('session.gc_maxlifetime');
try {
$updateStmt = $this->pdo->prepare(
"UPDATE $this->table SET $this->lifetimeCol = :expiry, $this->timeCol = :time WHERE $this->idCol = :id"
);
$updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR);
$updateStmt->bindParam(':expiry', $expiry, \PDO::PARAM_INT);
$updateStmt->bindValue(':id', $sessionId, \PDO::PARAM_STR);
$updateStmt->bindValue(':expiry', $expiry, \PDO::PARAM_INT);
$updateStmt->bindValue(':time', time(), \PDO::PARAM_INT);
$updateStmt->execute();
} catch (\PDOException $e) {
@@ -530,8 +530,8 @@ class PdoSessionHandler extends AbstractSessionHandler
return $dsn;
}
}
// If "unix_socket" is not in the query, we continue with the same process as pgsql
// no break
// If "unix_socket" is not in the query, we continue with the same process as pgsql
// no break
case 'pgsql':
$dsn ?? $dsn = 'pgsql:';
@@ -687,7 +687,7 @@ class PdoSessionHandler extends AbstractSessionHandler
throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.');
}
if (!filter_var(ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
if (!filter_var(\ini_get('session.use_strict_mode'), \FILTER_VALIDATE_BOOLEAN) && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
// In strict mode, session fixation is not possible: new sessions always start with a unique
// random id, so that concurrency is not possible and this code path can be skipped.
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
@@ -935,7 +935,7 @@ class PdoSessionHandler extends AbstractSessionHandler
protected function getConnection()
{
if (null === $this->pdo) {
$this->connect($this->dsn ?: ini_get('session.save_path'));
$this->connect($this->dsn ?: \ini_get('session.save_path'));
}
return $this->pdo;

View File

@@ -79,7 +79,7 @@ class RedisSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data): bool
{
$result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')), $data);
$result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data);
return $result && !$result instanceof ErrorInterface;
}
@@ -132,6 +132,6 @@ class RedisSessionHandler extends AbstractSessionHandler
#[\ReturnTypeWillChange]
public function updateTimestamp($sessionId, $data)
{
return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? ini_get('session.gc_maxlifetime')));
return (bool) $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')));
}
}

View File

@@ -11,7 +11,10 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
use Doctrine\DBAL\Tools\DsnParser;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
use Symfony\Component\Cache\Traits\RedisClusterProxy;
use Symfony\Component\Cache\Traits\RedisProxy;
@@ -60,7 +63,7 @@ class SessionHandlerFactory
case str_starts_with($connection, 'rediss:'):
case str_starts_with($connection, 'memcached:'):
if (!class_exists(AbstractAdapter::class)) {
throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection));
throw new \InvalidArgumentException('Unsupported Redis or Memcached DSN. Try running "composer require symfony/cache".');
}
$handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
@@ -69,9 +72,18 @@ class SessionHandlerFactory
case str_starts_with($connection, 'pdo_oci://'):
if (!class_exists(DriverManager::class)) {
throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require doctrine/dbal".', $connection));
throw new \InvalidArgumentException('Unsupported PDO OCI DSN. Try running "composer require doctrine/dbal".');
}
$connection = DriverManager::getConnection(['url' => $connection])->getWrappedConnection();
$connection[3] = '-';
$params = class_exists(DsnParser::class) ? (new DsnParser())->parse($connection) : ['url' => $connection];
$config = new Configuration();
if (class_exists(DefaultSchemaManagerFactory::class)) {
$config->setSchemaManagerFactory(new DefaultSchemaManagerFactory());
}
$connection = DriverManager::getConnection($params, $config);
// The condition should be removed once support for DBAL <3.3 is dropped
$connection = method_exists($connection, 'getNativeConnection') ? $connection->getNativeConnection() : $connection->getWrappedConnection();
// no break;
case str_starts_with($connection, 'mssql://'):

View File

@@ -30,6 +30,16 @@ class StrictSessionHandler extends AbstractSessionHandler
$this->handler = $handler;
}
/**
* Returns true if this handler wraps an internal PHP session save handler using \SessionHandler.
*
* @internal
*/
public function isWrapper(): bool
{
return $this->handler instanceof \SessionHandler;
}
/**
* @return bool
*/

View File

@@ -95,12 +95,12 @@ class MetadataBag implements SessionBagInterface
/**
* Stamps a new session's metadata.
*
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
* @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*/
public function stampNew(int $lifetime = null)
public function stampNew(?int $lifetime = null)
{
$this->stampCreated($lifetime);
}
@@ -158,10 +158,10 @@ class MetadataBag implements SessionBagInterface
$this->name = $name;
}
private function stampCreated(int $lifetime = null): void
private function stampCreated(?int $lifetime = null): void
{
$timeStamp = time();
$this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp;
$this->meta[self::LIFETIME] = $lifetime ?? (int) ini_get('session.cookie_lifetime');
$this->meta[self::LIFETIME] = $lifetime ?? (int) \ini_get('session.cookie_lifetime');
}
}

View File

@@ -62,7 +62,7 @@ class MockArraySessionStorage implements SessionStorageInterface
*/
protected $bags = [];
public function __construct(string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
public function __construct(string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
{
$this->name = $name;
$this->setMetadataBag($metaBag);
@@ -94,7 +94,7 @@ class MockArraySessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function regenerate(bool $destroy = false, int $lifetime = null)
public function regenerate(bool $destroy = false, ?int $lifetime = null)
{
if (!$this->started) {
$this->start();
@@ -204,7 +204,7 @@ class MockArraySessionStorage implements SessionStorageInterface
return $this->started;
}
public function setMetadataBag(MetadataBag $bag = null)
public function setMetadataBag(?MetadataBag $bag = null)
{
if (null === $bag) {
$bag = new MetadataBag();

View File

@@ -30,7 +30,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
/**
* @param string|null $savePath Path of directory to save session files
*/
public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
{
if (null === $savePath) {
$savePath = sys_get_temp_dir();
@@ -68,7 +68,7 @@ class MockFileSessionStorage extends MockArraySessionStorage
/**
* {@inheritdoc}
*/
public function regenerate(bool $destroy = false, int $lifetime = null)
public function regenerate(bool $destroy = false, ?int $lifetime = null)
{
if (!$this->started) {
$this->start();

View File

@@ -28,7 +28,7 @@ class MockFileSessionStorageFactory implements SessionStorageFactoryInterface
/**
* @see MockFileSessionStorage constructor.
*/
public function __construct(string $savePath = null, string $name = 'MOCKSESSID', MetadataBag $metaBag = null)
public function __construct(?string $savePath = null, string $name = 'MOCKSESSID', ?MetadataBag $metaBag = null)
{
$this->savePath = $savePath;
$this->name = $name;

View File

@@ -97,7 +97,7 @@ class NativeSessionStorage implements SessionStorageInterface
*
* @param AbstractProxy|\SessionHandlerInterface|null $handler
*/
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null)
public function __construct(array $options = [], $handler = null, ?MetadataBag $metaBag = null)
{
if (!\extension_loaded('session')) {
throw new \LogicException('PHP extension "session" is required.');
@@ -141,10 +141,46 @@ class NativeSessionStorage implements SessionStorageInterface
throw new \RuntimeException('Failed to start the session: already started by PHP.');
}
if (filter_var(ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
if (filter_var(\ini_get('session.use_cookies'), \FILTER_VALIDATE_BOOLEAN) && headers_sent($file, $line)) {
throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
}
$sessionId = $_COOKIE[session_name()] ?? null;
/*
* Explanation of the session ID regular expression: `/^[a-zA-Z0-9,-]{22,250}$/`.
*
* ---------- Part 1
*
* The part `[a-zA-Z0-9,-]` is related to the PHP ini directive `session.sid_bits_per_character` defined as 6.
* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character.
* Allowed values are integers such as:
* - 4 for range `a-f0-9`
* - 5 for range `a-v0-9`
* - 6 for range `a-zA-Z0-9,-`
*
* ---------- Part 2
*
* The part `{22,250}` is related to the PHP ini directive `session.sid_length`.
* See https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length.
* Allowed values are integers between 22 and 256, but we use 250 for the max.
*
* Where does the 250 come from?
* - The length of Windows and Linux filenames is limited to 255 bytes. Then the max must not exceed 255.
* - The session filename prefix is `sess_`, a 5 bytes string. Then the max must not exceed 255 - 5 = 250.
*
* ---------- Conclusion
*
* The parts 1 and 2 prevent the warning below:
* `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.`
*
* The part 2 prevents the warning below:
* `PHP Warning: SessionHandler::read(): open(filepath, O_RDWR) failed: No such file or directory (2).`
*/
if ($sessionId && $this->saveHandler instanceof AbstractProxy && 'files' === $this->saveHandler->getSaveHandlerName() && !preg_match('/^[a-zA-Z0-9,-]{22,250}$/', $sessionId)) {
// the session ID in the header is invalid, create a new one
session_id(session_create_id());
}
// ok to try and start the session
if (!session_start()) {
throw new \RuntimeException('Failed to start the session.');
@@ -197,7 +233,7 @@ class NativeSessionStorage implements SessionStorageInterface
/**
* {@inheritdoc}
*/
public function regenerate(bool $destroy = false, int $lifetime = null)
public function regenerate(bool $destroy = false, ?int $lifetime = null)
{
// Cannot regenerate the session ID for non-active sessions.
if (\PHP_SESSION_ACTIVE !== session_status()) {
@@ -208,7 +244,7 @@ class NativeSessionStorage implements SessionStorageInterface
return false;
}
if (null !== $lifetime && $lifetime != ini_get('session.cookie_lifetime')) {
if (null !== $lifetime && $lifetime != \ini_get('session.cookie_lifetime')) {
$this->save();
ini_set('session.cookie_lifetime', $lifetime);
$this->start();
@@ -243,7 +279,7 @@ class NativeSessionStorage implements SessionStorageInterface
unset($_SESSION[$key]);
}
}
if ([$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
if ($_SESSION && [$key = $this->metadataBag->getStorageKey()] === array_keys($_SESSION)) {
unset($_SESSION[$key]);
}
@@ -319,7 +355,7 @@ class NativeSessionStorage implements SessionStorageInterface
return $this->bags[$name];
}
public function setMetadataBag(MetadataBag $metaBag = null)
public function setMetadataBag(?MetadataBag $metaBag = null)
{
if (null === $metaBag) {
$metaBag = new MetadataBag();
@@ -419,9 +455,10 @@ class NativeSessionStorage implements SessionStorageInterface
*/
public function setSaveHandler($saveHandler = null)
{
if (!$saveHandler instanceof AbstractProxy &&
!$saveHandler instanceof \SessionHandlerInterface &&
null !== $saveHandler) {
if (!$saveHandler instanceof AbstractProxy
&& !$saveHandler instanceof \SessionHandlerInterface
&& null !== $saveHandler
) {
throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.');
}
@@ -450,7 +487,7 @@ class NativeSessionStorage implements SessionStorageInterface
* PHP takes the return value from the read() handler, unserializes it
* and populates $_SESSION with the result automatically.
*/
protected function loadSession(array &$session = null)
protected function loadSession(?array &$session = null)
{
if (null === $session) {
$session = &$_SESSION;

View File

@@ -29,7 +29,7 @@ class NativeSessionStorageFactory implements SessionStorageFactoryInterface
/**
* @see NativeSessionStorage constructor.
*/
public function __construct(array $options = [], $handler = null, MetadataBag $metaBag = null, bool $secure = false)
public function __construct(array $options = [], $handler = null, ?MetadataBag $metaBag = null, bool $secure = false)
{
$this->options = $options;
$this->handler = $handler;

View File

@@ -23,7 +23,7 @@ class PhpBridgeSessionStorage extends NativeSessionStorage
/**
* @param AbstractProxy|\SessionHandlerInterface|null $handler
*/
public function __construct($handler = null, MetadataBag $metaBag = null)
public function __construct($handler = null, ?MetadataBag $metaBag = null)
{
if (!\extension_loaded('session')) {
throw new \LogicException('PHP extension "session" is required.');

View File

@@ -28,7 +28,7 @@ class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface
/**
* @see PhpBridgeSessionStorage constructor.
*/
public function __construct($handler = null, MetadataBag $metaBag = null, bool $secure = false)
public function __construct($handler = null, ?MetadataBag $metaBag = null, bool $secure = false)
{
$this->handler = $handler;
$this->metaBag = $metaBag;

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
/**
* @author Drak <drak@zikula.org>
*/
@@ -22,7 +24,7 @@ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterf
{
$this->handler = $handler;
$this->wrapper = $handler instanceof \SessionHandler;
$this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user';
$this->saveHandlerName = $this->wrapper || ($handler instanceof StrictSessionHandler && $handler->isWrapper()) ? \ini_get('session.save_handler') : 'user';
}
/**

View File

@@ -80,17 +80,17 @@ interface SessionStorageInterface
* Otherwise session data could get lost again for concurrent requests with the
* new ID. One result could be that you get logged out after just logging in.
*
* @param bool $destroy Destroy session when regenerating?
* @param int $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
* @param bool $destroy Destroy session when regenerating?
* @param int|null $lifetime Sets the cookie lifetime for the session cookie. A null value
* will leave the system settings unchanged, 0 sets the cookie
* to expire with browser session. Time is in seconds, and is
* not a Unix timestamp.
*
* @return bool
*
* @throws \RuntimeException If an error occurs while regenerating this storage
*/
public function regenerate(bool $destroy = false, int $lifetime = null);
public function regenerate(bool $destroy = false, ?int $lifetime = null);
/**
* Force the session to be saved and closed.

View File

@@ -30,7 +30,7 @@ class StreamedResponse extends Response
protected $streamed;
private $headersSent;
public function __construct(callable $callback = null, int $status = 200, array $headers = [])
public function __construct(?callable $callback = null, int $status = 200, array $headers = [])
{
parent::__construct(null, $status, $headers);
@@ -114,9 +114,9 @@ class StreamedResponse extends Response
/**
* {@inheritdoc}
*
* @throws \LogicException when the content is not null
*
* @return $this
*
* @throws \LogicException when the content is not null
*/
public function setContent(?string $content)
{

View File

@@ -22,7 +22,7 @@ final class ResponseCookieValueSame extends Constraint
private $path;
private $domain;
public function __construct(string $name, string $value, string $path = '/', string $domain = null)
public function __construct(string $name, string $value, string $path = '/', ?string $domain = null)
{
$this->name = $name;
$this->value = $value;
@@ -59,7 +59,7 @@ final class ResponseCookieValueSame extends Constraint
return false;
}
return $this->value === $cookie->getValue();
return $this->value === (string) $cookie->getValue();
}
/**

View File

@@ -21,7 +21,7 @@ final class ResponseHasCookie extends Constraint
private $path;
private $domain;
public function __construct(string $name, string $path = '/', string $domain = null)
public function __construct(string $name, string $path = '/', ?string $domain = null)
{
$this->name = $name;
$this->path = $path;

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpFoundation;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RequestContextAwareInterface;
/**
* A helper service for manipulating URLs within and outside the request scope.
@@ -23,8 +24,15 @@ final class UrlHelper
private $requestStack;
private $requestContext;
public function __construct(RequestStack $requestStack, RequestContext $requestContext = null)
/**
* @param RequestContextAwareInterface|RequestContext|null $requestContext
*/
public function __construct(RequestStack $requestStack, $requestContext = null)
{
if (null !== $requestContext && !$requestContext instanceof RequestContext && !$requestContext instanceof RequestContextAwareInterface) {
throw new \TypeError(__METHOD__.': Argument #2 ($requestContext) must of type Symfony\Component\Routing\RequestContextAwareInterface|Symfony\Component\Routing\RequestContext|null, '.get_debug_type($requestContext).' given.');
}
$this->requestStack = $requestStack;
$this->requestContext = $requestContext;
}
@@ -73,28 +81,36 @@ final class UrlHelper
private function getAbsoluteUrlFromContext(string $path): string
{
if (null === $this->requestContext || '' === $host = $this->requestContext->getHost()) {
if (null === $context = $this->requestContext) {
return $path;
}
$scheme = $this->requestContext->getScheme();
if ($context instanceof RequestContextAwareInterface) {
$context = $context->getContext();
}
if ('' === $host = $context->getHost()) {
return $path;
}
$scheme = $context->getScheme();
$port = '';
if ('http' === $scheme && 80 !== $this->requestContext->getHttpPort()) {
$port = ':'.$this->requestContext->getHttpPort();
} elseif ('https' === $scheme && 443 !== $this->requestContext->getHttpsPort()) {
$port = ':'.$this->requestContext->getHttpsPort();
if ('http' === $scheme && 80 !== $context->getHttpPort()) {
$port = ':'.$context->getHttpPort();
} elseif ('https' === $scheme && 443 !== $context->getHttpsPort()) {
$port = ':'.$context->getHttpsPort();
}
if ('#' === $path[0]) {
$queryString = $this->requestContext->getQueryString();
$path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path;
$queryString = $context->getQueryString();
$path = $context->getPathInfo().($queryString ? '?'.$queryString : '').$path;
} elseif ('?' === $path[0]) {
$path = $this->requestContext->getPathInfo().$path;
$path = $context->getPathInfo().$path;
}
if ('/' !== $path[0]) {
$path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path;
$path = rtrim($context->getBaseUrl(), '/').'/'.$path;
}
return $scheme.'://'.$host.$port.$path;

View File

@@ -24,8 +24,11 @@
"require-dev": {
"predis/predis": "~1.0",
"symfony/cache": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4",
"symfony/mime": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0"
"symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/rate-limiter": "^5.2|^6.0"
},
"suggest" : {
"symfony/mime": "To use the file extension guesser"