From 826fbe10c8cdeaa48d38b4f729adaa26149befcd Mon Sep 17 00:00:00 2001 From: Molkobain Date: Wed, 22 Feb 2023 22:38:15 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B06002=20-=20Explicitly=20add=20symfony/ht?= =?UTF-8?q?tp-foundation=20and=20symfony/http-kernel=20to=20composer.json?= =?UTF-8?q?=20for=20easier=20lib=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 2 + composer.lock | 31 +++---- lib/composer/installed.json | 33 ++++---- lib/composer/installed.php | 16 ++-- .../http-foundation/BinaryFileResponse.php | 83 +++++++++++-------- .../Exception/SessionNotFoundException.php | 2 +- lib/symfony/http-foundation/IpUtils.php | 15 +++- lib/symfony/http-foundation/LICENSE | 2 +- .../AbstractRequestRateLimiter.php | 20 ++++- lib/symfony/http-foundation/Request.php | 13 +-- lib/symfony/http-foundation/Response.php | 2 +- .../Handler/MemcachedSessionHandler.php | 17 +++- .../Storage/Handler/StrictSessionHandler.php | 10 +++ .../Storage/Proxy/SessionHandlerProxy.php | 4 +- lib/symfony/http-foundation/composer.json | 5 +- .../NotTaggedControllerValueResolver.php | 5 +- .../ArgumentMetadataFactory.php | 2 +- .../DataCollector/LoggerDataCollector.php | 2 +- .../DataCollector/RequestDataCollector.php | 4 +- ...RegisterControllerArgumentLocatorsPass.php | 3 +- .../EventListener/AbstractSessionListener.php | 10 ++- .../EventListener/ErrorListener.php | 6 ++ lib/symfony/http-kernel/HttpCache/Store.php | 17 +++- lib/symfony/http-kernel/HttpKernel.php | 18 +++- lib/symfony/http-kernel/Kernel.php | 6 +- lib/symfony/http-kernel/LICENSE | 2 +- lib/symfony/http-kernel/Log/Logger.php | 16 ++-- .../Profiler/FileProfilerStorage.php | 60 +++++++------- 28 files changed, 262 insertions(+), 144 deletions(-) diff --git a/composer.json b/composer.json index 970f8f5fb..1f62d1526 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,8 @@ "symfony/console": "5.4.*", "symfony/dotenv": "5.4.*", "symfony/framework-bundle": "5.4.*", + "symfony/http-foundation": "5.4.*", + "symfony/http-kernel": "5.4.*", "symfony/twig-bundle": "5.4.*", "symfony/yaml": "5.4.*", "thenetworg/oauth2-azure": "^2.0" diff --git a/composer.lock b/composer.lock index 6058e8d9c..f1ec34304 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1fee7c7fd7649286a09641ae53e216de", + "content-hash": "778cb837668f4ab5686021bf761d0a83", "packages": [ { "name": "apereo/phpcas", @@ -3214,16 +3214,16 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.11", + "version": "v5.4.20", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389" + "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389", - "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0435363362a47c14e9cf50663cb8ffbf491875a", + "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a", "shasum": "" }, "require": { @@ -3235,8 +3235,11 @@ "require-dev": { "predis/predis": "~1.0", "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0" + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" }, "suggest": { "symfony/mime": "To use the file extension guesser" @@ -3267,7 +3270,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.11" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.20" }, "funding": [ { @@ -3283,20 +3286,20 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-01-29T11:11:52+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.11", + "version": "v5.4.20", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee" + "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4fd590a2ef3f62560dbbf6cea511995dd77321ee", - "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aaeec341582d3c160cc9ecfa8b2419ba6c69954e", + "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e", "shasum": "" }, "require": { @@ -3379,7 +3382,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.11" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.20" }, "funding": [ { @@ -3395,7 +3398,7 @@ "type": "tidelift" } ], - "time": "2022-07-29T12:30:22+00:00" + "time": "2023-02-01T08:18:48+00:00" }, { "name": "symfony/polyfill-ctype", diff --git a/lib/composer/installed.json b/lib/composer/installed.json index 8b5ee2b36..aeb12fea1 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -3340,17 +3340,17 @@ }, { "name": "symfony/http-foundation", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v5.4.20", + "version_normalized": "5.4.20.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389" + "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/0a5868e0999e9d47859ba3d918548ff6943e6389", - "reference": "0a5868e0999e9d47859ba3d918548ff6943e6389", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0435363362a47c14e9cf50663cb8ffbf491875a", + "reference": "d0435363362a47c14e9cf50663cb8ffbf491875a", "shasum": "" }, "require": { @@ -3362,13 +3362,16 @@ "require-dev": { "predis/predis": "~1.0", "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0" + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" }, "suggest": { "symfony/mime": "To use the file extension guesser" }, - "time": "2022-07-20T13:00:38+00:00", + "time": "2023-01-29T11:11:52+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3396,7 +3399,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.11" + "source": "https://github.com/symfony/http-foundation/tree/v5.4.20" }, "funding": [ { @@ -3416,17 +3419,17 @@ }, { "name": "symfony/http-kernel", - "version": "v5.4.11", - "version_normalized": "5.4.11.0", + "version": "v5.4.20", + "version_normalized": "5.4.20.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee" + "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4fd590a2ef3f62560dbbf6cea511995dd77321ee", - "reference": "4fd590a2ef3f62560dbbf6cea511995dd77321ee", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/aaeec341582d3c160cc9ecfa8b2419ba6c69954e", + "reference": "aaeec341582d3c160cc9ecfa8b2419ba6c69954e", "shasum": "" }, "require": { @@ -3483,7 +3486,7 @@ "symfony/console": "", "symfony/dependency-injection": "" }, - "time": "2022-07-29T12:30:22+00:00", + "time": "2023-02-01T08:18:48+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -3511,7 +3514,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.11" + "source": "https://github.com/symfony/http-kernel/tree/v5.4.20" }, "funding": [ { diff --git a/lib/composer/installed.php b/lib/composer/installed.php index 6527429f6..5006cea09 100644 --- a/lib/composer/installed.php +++ b/lib/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '5ee603f2ba067f521d8b98a14c42d09820dec2fb', + 'reference' => 'd997e36de0f7ad4a820dbae8851c60f55fcd1c25', 'name' => 'combodo/itop', 'dev' => true, ), @@ -25,7 +25,7 @@ 'type' => 'project', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '5ee603f2ba067f521d8b98a14c42d09820dec2fb', + 'reference' => 'd997e36de0f7ad4a820dbae8851c60f55fcd1c25', 'dev_requirement' => false, ), 'combodo/tcpdf' => array( @@ -498,21 +498,21 @@ 'dev_requirement' => false, ), 'symfony/http-foundation' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', + 'pretty_version' => 'v5.4.20', + 'version' => '5.4.20.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-foundation', 'aliases' => array(), - 'reference' => '0a5868e0999e9d47859ba3d918548ff6943e6389', + 'reference' => 'd0435363362a47c14e9cf50663cb8ffbf491875a', 'dev_requirement' => false, ), 'symfony/http-kernel' => array( - 'pretty_version' => 'v5.4.11', - 'version' => '5.4.11.0', + 'pretty_version' => 'v5.4.20', + 'version' => '5.4.20.0', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/http-kernel', 'aliases' => array(), - 'reference' => '4fd590a2ef3f62560dbbf6cea511995dd77321ee', + 'reference' => 'aaeec341582d3c160cc9ecfa8b2419ba6c69954e', 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( diff --git a/lib/symfony/http-foundation/BinaryFileResponse.php b/lib/symfony/http-foundation/BinaryFileResponse.php index beb0eda0a..6d7b80ad1 100644 --- a/lib/symfony/http-foundation/BinaryFileResponse.php +++ b/lib/symfony/http-foundation/BinaryFileResponse.php @@ -206,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; @@ -222,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')) { @@ -291,6 +296,10 @@ class BinaryFileResponse extends Response } } + if ($request->isMethod('HEAD')) { + $this->maxlen = 0; + } + return $this; } @@ -312,40 +321,42 @@ class BinaryFileResponse extends Response */ public function sendContent() { - if (!$this->isSuccessful()) { - return parent::sendContent(); - } - - if (0 === $this->maxlen) { - return $this; - } - - $out = fopen('php://output', 'w'); - $file = fopen($this->file->getPathname(), 'r'); - - ignore_user_abort(true); - - if (0 !== $this->offset) { - fseek($file, $this->offset); - } - - $length = $this->maxlen; - while ($length && !feof($file)) { - $read = ($length > $this->chunkSize) ? $this->chunkSize : $length; - $length -= $read; - - stream_copy_to_stream($file, $out, $read); - - if (connection_aborted()) { - break; + try { + if (!$this->isSuccessful()) { + return parent::sendContent(); } - } - fclose($out); - fclose($file); + if (0 === $this->maxlen) { + return $this; + } - if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { - unlink($this->file->getPathname()); + $out = fopen('php://output', 'w'); + $file = fopen($this->file->getPathname(), 'r'); + + ignore_user_abort(true); + + if (0 !== $this->offset) { + fseek($file, $this->offset); + } + + $length = $this->maxlen; + while ($length && !feof($file)) { + $read = ($length > $this->chunkSize) ? $this->chunkSize : $length; + $length -= $read; + + stream_copy_to_stream($file, $out, $read); + + if (connection_aborted()) { + break; + } + } + + fclose($out); + fclose($file); + } finally { + if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) { + unlink($this->file->getPathname()); + } } return $this; diff --git a/lib/symfony/http-foundation/Exception/SessionNotFoundException.php b/lib/symfony/http-foundation/Exception/SessionNotFoundException.php index 9c719aa04..94b0cb69a 100644 --- a/lib/symfony/http-foundation/Exception/SessionNotFoundException.php +++ b/lib/symfony/http-foundation/Exception/SessionNotFoundException.php @@ -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). * diff --git a/lib/symfony/http-foundation/IpUtils.php b/lib/symfony/http-foundation/IpUtils.php index 24111f1ec..2f31284e3 100644 --- a/lib/symfony/http-foundation/IpUtils.php +++ b/lib/symfony/http-foundation/IpUtils.php @@ -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) { @@ -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; } diff --git a/lib/symfony/http-foundation/LICENSE b/lib/symfony/http-foundation/LICENSE index 88bf75bb4..008370457 100644 --- a/lib/symfony/http-foundation/LICENSE +++ b/lib/symfony/http-foundation/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-2023 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 diff --git a/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php b/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php index c91d614fe..a6dd993b7 100644 --- a/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php +++ b/lib/symfony/http-foundation/RateLimiter/AbstractRequestRateLimiter.php @@ -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; + } } diff --git a/lib/symfony/http-foundation/Request.php b/lib/symfony/http-foundation/Request.php index 65f30a1a7..10f779d27 100644 --- a/lib/symfony/http-foundation/Request.php +++ b/lib/symfony/http-foundation/Request.php @@ -522,10 +522,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"; } @@ -1689,7 +1689,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 +1728,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 +1742,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 +1756,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())); } /** diff --git a/lib/symfony/http-foundation/Response.php b/lib/symfony/http-foundation/Response.php index 88635bb49..d5c8cb45c 100644 --- a/lib/symfony/http-foundation/Response.php +++ b/lib/symfony/http-foundation/Response.php @@ -399,6 +399,7 @@ class Response litespeed_finish_request(); } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { static::closeOutputBuffers(0, true); + flush(); } return $this; @@ -1245,7 +1246,6 @@ class Response while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { if ($flush) { ob_end_flush(); - flush(); } else { ob_end_clean(); } diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php index 9cb841ae0..e0ec4d2d9 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -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; } /** diff --git a/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php b/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php index 0461e997e..f7c385f64 100644 --- a/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php +++ b/lib/symfony/http-foundation/Session/Storage/Handler/StrictSessionHandler.php @@ -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 */ diff --git a/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php index 6539acf98..0defa4a7a 100644 --- a/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/lib/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -11,6 +11,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; + /** * @author Drak */ @@ -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'; } /** diff --git a/lib/symfony/http-foundation/composer.json b/lib/symfony/http-foundation/composer.json index d54bbfd16..cb8d59ffe 100644 --- a/lib/symfony/http-foundation/composer.json +++ b/lib/symfony/http-foundation/composer.json @@ -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" diff --git a/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php b/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php index d4971cc1a..48ea6e742 100644 --- a/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php +++ b/lib/symfony/http-kernel/Controller/ArgumentResolver/NotTaggedControllerValueResolver.php @@ -69,8 +69,9 @@ final class NotTaggedControllerValueResolver implements ArgumentValueResolverInt } if (!$this->container->has($controller)) { - $i = strrpos($controller, ':'); - $controller = substr($controller, 0, $i).strtolower(substr($controller, $i)); + $controller = (false !== $i = strrpos($controller, ':')) + ? substr($controller, 0, $i).strtolower(substr($controller, $i)) + : $controller.'::__invoke'; } $what = sprintf('argument $%s of "%s()"', $argument->getName(), $controller); diff --git a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php index e3835c057..85bb805f3 100644 --- a/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php +++ b/lib/symfony/http-kernel/ControllerMetadata/ArgumentMetadataFactory.php @@ -33,7 +33,7 @@ final class ArgumentMetadataFactory implements ArgumentMetadataFactoryInterface $class = $reflection->class; } else { $reflection = new \ReflectionFunction($controller); - if ($class = str_contains($reflection->name, '{closure}') ? null : $reflection->getClosureScopeClass()) { + if ($class = str_contains($reflection->name, '{closure}') ? null : (\PHP_VERSION_ID >= 80111 ? $reflection->getClosureCalledClass() : $reflection->getClosureScopeClass())) { $class = $class->name; } } diff --git a/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php index 15094fdbe..2bbd2a039 100644 --- a/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -144,7 +144,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte $allChannels = []; foreach ($this->getProcessedLogs() as $log) { - if ('' === trim($log['channel'])) { + if ('' === trim($log['channel'] ?? '')) { continue; } diff --git a/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php b/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php index 523f5c957..5717000f2 100644 --- a/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php +++ b/lib/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -110,7 +110,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter 'session_metadata' => $sessionMetadata, 'session_attributes' => $sessionAttributes, 'session_usages' => array_values($this->sessionUsages), - 'stateless_check' => $this->requestStack && $this->requestStack->getMainRequest()->attributes->get('_stateless', false), + 'stateless_check' => $this->requestStack && ($mainRequest = $this->requestStack->getMainRequest()) && $mainRequest->attributes->get('_stateless', false), 'flashes' => $flashes, 'path_info' => $request->getPathInfo(), 'controller' => 'n/a', @@ -479,7 +479,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter } $controller['method'] = $r->name; - if ($class = $r->getClosureScopeClass()) { + if ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { $controller['class'] = $class->name; } else { return $r->name; diff --git a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index 8fd1f553e..3dbaff564 100644 --- a/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/lib/symfony/http-kernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -24,6 +24,7 @@ use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; /** @@ -174,7 +175,7 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface $invalidBehavior = ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE; } - if (Request::class === $type || SessionInterface::class === $type) { + if (Request::class === $type || SessionInterface::class === $type || Response::class === $type) { continue; } diff --git a/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php b/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php index d0e8f45d0..27749b24b 100644 --- a/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php +++ b/lib/symfony/http-kernel/EventListener/AbstractSessionListener.php @@ -158,6 +158,11 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese $isSessionEmpty = $session->isEmpty() && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions if ($requestSessionCookieId && $isSessionEmpty) { + // PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument + // which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy + // when the session gets invalidated (for example on logout) so we must handle this case here too + // otherwise we would send two Set-Cookie headers back with the response + SessionUtils::popSessionCookie($sessionName, 'deleted'); $response->headers->clearCookie( $sessionName, $sessionCookiePath, @@ -195,10 +200,11 @@ abstract class AbstractSessionListener implements EventSubscriberInterface, Rese } if ($autoCacheControl) { + $maxAge = $response->headers->hasCacheControlDirective('public') ? 0 : (int) $response->getMaxAge(); $response - ->setExpires(new \DateTime()) + ->setExpires(new \DateTimeImmutable('+'.$maxAge.' seconds')) ->setPrivate() - ->setMaxAge(0) + ->setMaxAge($maxAge) ->headers->addCacheControlDirective('must-revalidate'); } diff --git a/lib/symfony/http-kernel/EventListener/ErrorListener.php b/lib/symfony/http-kernel/EventListener/ErrorListener.php index 9dc3871c2..b6fd0a357 100644 --- a/lib/symfony/http-kernel/EventListener/ErrorListener.php +++ b/lib/symfony/http-kernel/EventListener/ErrorListener.php @@ -33,8 +33,14 @@ class ErrorListener implements EventSubscriberInterface protected $controller; protected $logger; protected $debug; + /** + * @var array|null}> + */ protected $exceptionsMapping; + /** + * @param array|null}> $exceptionsMapping + */ public function __construct($controller, LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = []) { $this->controller = $controller; diff --git a/lib/symfony/http-kernel/HttpCache/Store.php b/lib/symfony/http-kernel/HttpCache/Store.php index c77739138..8087e0cb1 100644 --- a/lib/symfony/http-kernel/HttpCache/Store.php +++ b/lib/symfony/http-kernel/HttpCache/Store.php @@ -29,17 +29,28 @@ class Store implements StoreInterface private $keyCache; /** @var array */ private $locks = []; + private $options; /** + * Constructor. + * + * The available options are: + * + * * private_headers Set of response headers that should not be stored + * when a response is cached. (default: Set-Cookie) + * * @throws \RuntimeException */ - public function __construct(string $root) + public function __construct(string $root, array $options = []) { $this->root = $root; if (!is_dir($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); } $this->keyCache = new \SplObjectStorage(); + $this->options = array_merge([ + 'private_headers' => ['Set-Cookie'], + ], $options); } /** @@ -216,6 +227,10 @@ class Store implements StoreInterface $headers = $this->persistResponse($response); unset($headers['age']); + foreach ($this->options['private_headers'] as $h) { + unset($headers[strtolower($h)]); + } + array_unshift($entries, [$storedEnv, $headers]); if (!$this->save($key, serialize($entries))) { diff --git a/lib/symfony/http-kernel/HttpKernel.php b/lib/symfony/http-kernel/HttpKernel.php index e10e68338..38102e252 100644 --- a/lib/symfony/http-kernel/HttpKernel.php +++ b/lib/symfony/http-kernel/HttpKernel.php @@ -70,6 +70,7 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); + $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Exception $e) { @@ -83,6 +84,8 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface } return $this->handleThrowable($e, $request, $type); + } finally { + $this->requestStack->pop(); } } @@ -103,7 +106,17 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface throw $exception; } - $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST); + if ($pop = $request !== $this->requestStack->getMainRequest()) { + $this->requestStack->push($request); + } + + try { + $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST); + } finally { + if ($pop) { + $this->requestStack->pop(); + } + } $response->sendHeaders(); $response->sendContent(); @@ -121,8 +134,6 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface */ private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { - $this->requestStack->push($request); - // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); @@ -199,7 +210,6 @@ class HttpKernel implements HttpKernelInterface, TerminableInterface private function finishRequest(Request $request, int $type) { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); - $this->requestStack->pop(); } /** diff --git a/lib/symfony/http-kernel/Kernel.php b/lib/symfony/http-kernel/Kernel.php index 358b92646..b2ccc7d95 100644 --- a/lib/symfony/http-kernel/Kernel.php +++ b/lib/symfony/http-kernel/Kernel.php @@ -78,11 +78,11 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ private static $freshCache = []; - public const VERSION = '5.4.11'; - public const VERSION_ID = 50411; + public const VERSION = '5.4.20'; + public const VERSION_ID = 50420; public const MAJOR_VERSION = 5; public const MINOR_VERSION = 4; - public const RELEASE_VERSION = 11; + public const RELEASE_VERSION = 20; public const EXTRA_VERSION = ''; public const END_OF_MAINTENANCE = '11/2024'; diff --git a/lib/symfony/http-kernel/LICENSE b/lib/symfony/http-kernel/LICENSE index 88bf75bb4..008370457 100644 --- a/lib/symfony/http-kernel/LICENSE +++ b/lib/symfony/http-kernel/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2022 Fabien Potencier +Copyright (c) 2004-2023 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 diff --git a/lib/symfony/http-kernel/Log/Logger.php b/lib/symfony/http-kernel/Log/Logger.php index 06ec0f96d..c2a45bb95 100644 --- a/lib/symfony/http-kernel/Log/Logger.php +++ b/lib/symfony/http-kernel/Log/Logger.php @@ -49,10 +49,14 @@ class Logger extends AbstractLogger if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { switch ((int) ($_ENV['SHELL_VERBOSITY'] ?? $_SERVER['SHELL_VERBOSITY'])) { - case -1: $minLevel = LogLevel::ERROR; break; - case 1: $minLevel = LogLevel::NOTICE; break; - case 2: $minLevel = LogLevel::INFO; break; - case 3: $minLevel = LogLevel::DEBUG; break; + case -1: $minLevel = LogLevel::ERROR; + break; + case 1: $minLevel = LogLevel::NOTICE; + break; + case 2: $minLevel = LogLevel::INFO; + break; + case 3: $minLevel = LogLevel::DEBUG; + break; } } } @@ -85,7 +89,7 @@ class Logger extends AbstractLogger $formatter = $this->formatter; if ($this->handle) { - @fwrite($this->handle, $formatter($level, $message, $context)); + @fwrite($this->handle, $formatter($level, $message, $context).\PHP_EOL); } else { error_log($formatter($level, $message, $context, false)); } @@ -110,7 +114,7 @@ class Logger extends AbstractLogger $message = strtr($message, $replacements); } - $log = sprintf('[%s] %s', $level, $message).\PHP_EOL; + $log = sprintf('[%s] %s', $level, $message); if ($prefixDate) { $log = date(\DateTime::RFC3339).' '.$log; } diff --git a/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php b/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php index 3e7b1bd88..0b5a780ab 100644 --- a/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php +++ b/lib/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -115,19 +115,7 @@ class FileProfilerStorage implements ProfilerStorageInterface */ public function read(string $token): ?Profile { - if (!$token || !file_exists($file = $this->getFilename($token))) { - return null; - } - - if (\function_exists('gzcompress')) { - $file = 'compress.zlib://'.$file; - } - - if (!$data = unserialize(file_get_contents($file))) { - return null; - } - - return $this->createProfileFromData($token, $data); + return $this->doRead($token); } /** @@ -169,14 +157,13 @@ class FileProfilerStorage implements ProfilerStorageInterface 'status_code' => $profile->getStatusCode(), ]; - $context = stream_context_create(); + $data = serialize($data); - if (\function_exists('gzcompress')) { - $file = 'compress.zlib://'.$file; - stream_context_set_option($context, 'zlib', 'level', 3); + if (\function_exists('gzencode')) { + $data = gzencode($data, 3); } - if (false === file_put_contents($file, serialize($data), 0, $context)) { + if (false === file_put_contents($file, $data, \LOCK_EX)) { return false; } @@ -291,21 +278,34 @@ class FileProfilerStorage implements ProfilerStorageInterface } foreach ($data['children'] as $token) { - if (!$token || !file_exists($file = $this->getFilename($token))) { - continue; + if (null !== $childProfile = $this->doRead($token, $profile)) { + $profile->addChild($childProfile); } - - if (\function_exists('gzcompress')) { - $file = 'compress.zlib://'.$file; - } - - if (!$childData = unserialize(file_get_contents($file))) { - continue; - } - - $profile->addChild($this->createProfileFromData($token, $childData, $profile)); } return $profile; } + + private function doRead($token, Profile $profile = null): ?Profile + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return null; + } + + $h = fopen($file, 'r'); + flock($h, \LOCK_SH); + $data = stream_get_contents($h); + flock($h, \LOCK_UN); + fclose($h); + + if (\function_exists('gzdecode')) { + $data = @gzdecode($data) ?: $data; + } + + if (!$data = unserialize($data)) { + return null; + } + + return $this->createProfileFromData($token, $data, $profile); + } }