diff --git a/CHANGELOG-6.3.md b/CHANGELOG-6.3.md index b42b158c1056c..d50e65ef16b3f 100644 --- a/CHANGELOG-6.3.md +++ b/CHANGELOG-6.3.md @@ -7,6 +7,32 @@ in 6.3 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v6.3.0...v6.3.1 +* 6.3.8 (2023-11-10) + + * bug #51666 [RateLimiter] CompoundLimiter was accepting requests even when some limiters already consumed all tokens (10n) + * security #cve-2023-46734 [TwigBridge] Ensure CodeExtension's filters properly escape their input (nicolas-grekas, GromNaN) + * security #cve-2023-46735 [Webhook] Remove user-submitted type from HTTP response (nicolas-grekas) + * security #cve-2023-46733 [Security] Fix possible session fixation when only the *token* changes (RobertMe) + * bug #52514 [FrameworkBundle] Don't reference SYMFONY_IDE env var in non-debug mode (nicolas-grekas) + * bug #52506 [SecurityBundle] wire the secret for Symfony 6.4 compatibility (xabbuh) + * bug #52496 [VarDumper] Accept mixed key on `DsPairStub` (marc-mabe) + * bug #52502 [Config] Prefixing `FileExistenceResource::__toString()` to avoid conflict with `FileResource` (weaverryan) + * bug #52491 [String] Method toByteString conversion using iconv is unreachable (Vincentv92) + * bug #52488 [HttpKernel] Fix PHP deprecation (nicolas-grekas) + * bug #52476 [Messenger] fix compatibility with Doctrine DBAL 4 (xabbuh) + * bug #52474 [HttpFoundation] ensure string type with mbstring func overloading enabled (xabbuh) + * bug #52472 [HttpClient][WebProfilerBundle] Do not generate cURL command when files are uploaded (MatTheCat) + * bug #52457 [Cache][HttpFoundation][Lock] Fix empty username/password for PDO PostgreSQL (HypeMC) + * bug #52443 [Yaml] Fix uid binary parsing (mRoca) + * bug #52429 [HttpClient] Replace `escapeshellarg` to prevent overpassing `ARG_MAX` (alexandre-daubois) + * bug #52442 Disable the "Copy as cURL" button when the debug info are disabled (stof) + * bug #52444 Remove full DSNs from exception messages (nicolas-grekas) + * bug #52428 [HttpKernel] Preventing error 500 when function putenv is disabled (ShaiMagal) + * bug #52408 [Yaml] Fix block scalar array parsing (NickSdot) + * bug #52132 [Console] Fix horizontal table top border is incorrectly rendered (OskarStark) + * bug #52367 [Uid] Fix UuidV7 collisions within the same ms (nicolas-grekas) + * bug #52222 [MonologBridge] Fix support for monolog 3.0 (louismariegaborit) + * 6.3.7 (2023-10-29) * bug #52329 [HttpClient] Psr18Client: parse HTTP Reason Phrase for Response (Hanmac) diff --git a/psalm.xml b/psalm.xml index f6ff0ea2ab6d5..a21be22fe248f 100644 --- a/psalm.xml +++ b/psalm.xml @@ -9,6 +9,7 @@ errorBaseline=".github/psalm/psalm.baseline.xml" findUnusedBaselineEntry="false" findUnusedCode="false" + findUnusedIssueHandlerSuppression="false" > diff --git a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php index 34d36c52ffb54..e025637f6cd8d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/DependencyInjection/DoctrineExtensionTest.php @@ -192,7 +192,7 @@ public function testMappingTypeDetection() $this->assertSame($mappingType, 'attribute'); } - public static function providerBasicDrivers() + public static function providerBasicDrivers(): array { return [ ['doctrine.orm.cache.apc.class', ['type' => 'apc']], @@ -271,7 +271,7 @@ public function testUnrecognizedCacheDriverException() $this->invokeLoadCacheDriver($objectManager, $container, $cacheName); } - public static function providerBundles() + public static function providerBundles(): iterable { yield ['AnnotationsBundle', 'attribute', '/Entity']; yield ['AnnotationsOneLineBundle', 'attribute', '/Entity']; diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php index 0f7066609f6fb..6ad6c3d1464d5 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/Type/EntityTypeTest.php @@ -158,7 +158,7 @@ public function testChoiceTranslationDomainIsDisabledByDefault($expanded) } } - public static function choiceTranslationDomainProvider() + public static function choiceTranslationDomainProvider(): array { return [ [false], @@ -238,8 +238,6 @@ public function testConfigureQueryBuilderWithClosureReturningNonQueryBuilder() 'class' => self::SINGLE_IDENT_CLASS, 'query_builder' => fn () => new \stdClass(), ]); - - $field->submit('2'); } public function testConfigureQueryBuilderWithClosureReturningNullUseDefault() diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index aa41a25125867..0f7a3f7022fc3 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -483,7 +483,7 @@ public function testValidateResultTypes($entity1, $result) $this->assertNoViolation(); } - public static function resultTypesProvider() + public static function resultTypesProvider(): array { $entity = new SingleIntIdEntity(1, 'foo'); diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index 5210e8eefafd5..42b0701cf61a9 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -13,7 +13,9 @@ use Monolog\Formatter\FormatterInterface; use Monolog\Handler\HandlerInterface; +use Monolog\Level; use Monolog\Logger; +use Monolog\LogRecord; use Symfony\Bridge\Monolog\Formatter\ConsoleFormatter; use Symfony\Bridge\Monolog\Handler\ConsoleHandler; use Symfony\Component\Console\Attribute\AsCommand; @@ -156,6 +158,16 @@ private function displayLog(OutputInterface $output, int $clientId, array $recor $logBlock = sprintf(' ', self::BG_COLOR[$clientId % 8]); $output->write($logBlock); + if (Logger::API >= 3) { + $record = new LogRecord( + $record['datetime'], + $record['channel'], + Level::fromValue($record['level']), + $record['message'], + $record['context']->getContext(), + ); + } + $this->handler->handle($record); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php index b2f8a79f965a7..6e91685e7bd86 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Handler/ConsoleHandlerTest.php @@ -82,7 +82,7 @@ public function testVerbosityMapping($verbosity, $level, $isHandling, array $map $this->assertFalse($handler->handle($infoRecord), 'The handler finished handling the log.'); } - public static function provideVerbosityMappingTests() + public static function provideVerbosityMappingTests(): array { return [ [OutputInterface::VERBOSITY_QUIET, Logger::ERROR, true], diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php index e7b731152daa6..f660a8060f962 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/ConfigurationTest.php @@ -249,8 +249,8 @@ public function testToleratesForIndividualGroups(string $deprecationsHelper, arr } } - public static function provideDataForToleratesForGroup() { - + public static function provideDataForToleratesForGroup(): iterable + { yield 'total threshold not reached' => ['max[total]=1', [ 'unsilenced' => 0, 'self' => 0, diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php index 5c7cf991b3f2f..01d418ef23829 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/DeprecationTest.php @@ -97,7 +97,7 @@ public function testItMutesOnlySpecificErrorMessagesWhenTheCallingCodeIsInPhpuni $this->assertSame($muted, $deprecation->isMuted()); } - public static function mutedProvider() + public static function mutedProvider(): iterable { yield 'not from phpunit, and not a whitelisted message' => [ false, @@ -147,7 +147,7 @@ public function testItTakesMutesDeprecationFromPhpUnitFiles() $this->assertTrue($deprecation->isMuted()); } - public static function providerGetTypeDetectsSelf() + public static function providerGetTypeDetectsSelf(): array { return [ 'not_from_vendors_file' => [Deprecation::TYPE_SELF, '', 'MyClass1', __FILE__], @@ -182,7 +182,7 @@ public function testGetTypeDetectsSelf(string $expectedType, string $message, st $this->assertSame($expectedType, $deprecation->getType()); } - public static function providerGetTypeUsesRightTrace() + public static function providerGetTypeUsesRightTrace(): array { $vendorDir = self::getVendorDir(); $fakeTrace = [ diff --git a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php index d6bb18e43b0c0..2fed9096b03b9 100644 --- a/src/Symfony/Bridge/Twig/Extension/CodeExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/CodeExtension.php @@ -36,8 +36,8 @@ public function __construct(string|FileLinkFormatter $fileLinkFormat, string $pr public function getFilters(): array { return [ - new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html']]), - new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html']]), + new TwigFilter('abbr_class', $this->abbrClass(...), ['is_safe' => ['html'], 'pre_escape' => 'html']), + new TwigFilter('abbr_method', $this->abbrMethod(...), ['is_safe' => ['html'], 'pre_escape' => 'html']), new TwigFilter('format_args', $this->formatArgs(...), ['is_safe' => ['html']]), new TwigFilter('format_args_as_text', $this->formatArgsAsText(...)), new TwigFilter('file_excerpt', $this->fileExcerpt(...), ['is_safe' => ['html']]), @@ -79,22 +79,23 @@ public function formatArgs(array $args): string $result = []; foreach ($args as $key => $item) { if ('object' === $item[0]) { + $item[1] = htmlspecialchars($item[1], \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); $parts = explode('\\', $item[1]); $short = array_pop($parts); $formattedValue = sprintf('object(%s)', $item[1], $short); } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)); } elseif ('null' === $item[0]) { $formattedValue = 'null'; } elseif ('boolean' === $item[0]) { - $formattedValue = ''.strtolower(var_export($item[1], true)).''; + $formattedValue = ''.strtolower(htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)).''; } elseif ('resource' === $item[0]) { $formattedValue = 'resource'; } else { $formattedValue = str_replace("\n", '', htmlspecialchars(var_export($item[1], true), \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset)); } - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", htmlspecialchars($key, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $formattedValue); } return implode(', ', $result); @@ -156,11 +157,14 @@ public function formatFile(string $file, int $line, string $text = null): string $file = trim($file); if (null === $text) { - $text = $file; - if (null !== $rel = $this->getFileRelative($text)) { - $rel = explode('/', $rel, 2); - $text = sprintf('%s%s', $this->projectDir, $rel[0], '/'.($rel[1] ?? '')); + if (null !== $rel = $this->getFileRelative($file)) { + $rel = explode('/', htmlspecialchars($rel, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), 2); + $text = sprintf('%s%s', htmlspecialchars($this->projectDir, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset), $rel[0], '/'.($rel[1] ?? '')); + } else { + $text = htmlspecialchars($file, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); } + } else { + $text = htmlspecialchars($text, \ENT_COMPAT | \ENT_SUBSTITUTE, $this->charset); } if (0 < $line) { diff --git a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php index 764eade4d9171..1cb98d9b5ce7d 100644 --- a/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php +++ b/src/Symfony/Bridge/Twig/Tests/AppVariableTest.php @@ -44,7 +44,7 @@ public function testDebug($debugFlag) $this->assertEquals($debugFlag, $this->appVariable->getDebug()); } - public static function debugDataProvider() + public static function debugDataProvider(): array { return [ 'debug on' => [true], diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index 75203f9c899e0..51bd80ab5f653 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -146,7 +146,7 @@ public function testComplete(array $input, array $expectedSuggestions) $this->assertSame($expectedSuggestions, $tester->complete($input)); } - public static function provideCompletionSuggestions() + public static function provideCompletionSuggestions(): iterable { yield 'option' => [['--format', ''], ['txt', 'json', 'github']]; } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php index 38983cbd697f1..c7c859f067c7a 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/CodeExtensionTest.php @@ -14,6 +14,8 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\CodeExtension; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Twig\Environment; +use Twig\Loader\ArrayLoader; class CodeExtensionTest extends TestCase { @@ -28,42 +30,136 @@ public function testFileRelative() $this->assertEquals('file.txt', $this->getExtension()->getFileRelative(\DIRECTORY_SEPARATOR.'project'.\DIRECTORY_SEPARATOR.'file.txt')); } - /** - * @dataProvider getClassNameProvider - */ - public function testGettingClassAbbreviation($class, $abbr) + public function testClassAbbreviationIntegration() { - $this->assertEquals($this->getExtension()->abbrClass($class), $abbr); + $data = [ + 'fqcn' => 'F\Q\N\Foo', + 'xss' => '