diff --git a/EventListener/ProfilerListener.php b/EventListener/ProfilerListener.php index 8fd17adc7d..3143f2b9a6 100644 --- a/EventListener/ProfilerListener.php +++ b/EventListener/ProfilerListener.php @@ -119,7 +119,7 @@ public function onKernelTerminate(TerminateEvent $event) public static function getSubscribedEvents(): array { return [ - KernelEvents::RESPONSE => ['onKernelResponse', -1012], + KernelEvents::RESPONSE => ['onKernelResponse', -100], KernelEvents::EXCEPTION => ['onKernelException', 0], KernelEvents::TERMINATE => ['onKernelTerminate', -1024], ]; diff --git a/HttpCache/Store.php b/HttpCache/Store.php index b7953e1303..96c310fe95 100644 --- a/HttpCache/Store.php +++ b/HttpCache/Store.php @@ -175,19 +175,15 @@ public function write(Request $request, Response $response) $key = $this->getCacheKey($request); $storedEnv = $this->persistRequest($request); - // write the response body to the entity store if this is the original response - if (!$response->headers->has('X-Content-Digest')) { - $digest = $this->generateContentDigest($response); + $digest = $this->generateContentDigest($response); + $response->headers->set('X-Content-Digest', $digest); - if (!$this->save($digest, $response->getContent())) { - throw new \RuntimeException('Unable to store the entity.'); - } - - $response->headers->set('X-Content-Digest', $digest); + if (!$this->save($digest, $response->getContent(), false)) { + throw new \RuntimeException('Unable to store the entity.'); + } - if (!$response->headers->has('Transfer-Encoding')) { - $response->headers->set('Content-Length', \strlen($response->getContent())); - } + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); } // read existing cache entries, remove non-varying, and add this one to the list @@ -344,10 +340,14 @@ private function load(string $key): ?string /** * Save data for the given key. */ - private function save(string $key, string $data): bool + private function save(string $key, string $data, bool $overwrite = true): bool { $path = $this->getPath($key); + if (!$overwrite && file_exists($path)) { + return true; + } + if (isset($this->locks[$key])) { $fp = $this->locks[$key]; @ftruncate($fp, 0); diff --git a/Kernel.php b/Kernel.php index 163e112077..b06cb82389 100644 --- a/Kernel.php +++ b/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.0-RC1'; + const VERSION = '5.1.0-RC2'; const VERSION_ID = 50100; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'RC1'; + const EXTRA_VERSION = 'RC2'; const END_OF_MAINTENANCE = '01/2021'; const END_OF_LIFE = '01/2021'; @@ -223,9 +223,7 @@ public function getBundles() public function getBundle(string $name) { if (!isset($this->bundles[$name])) { - $class = get_debug_type($this); - - throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, $class)); + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the "registerBundles()" method of your "%s.php" file?', $name, get_debug_type($this))); } return $this->bundles[$name]; @@ -400,6 +398,7 @@ protected function getContainerClass() $class = static::class; $class = false !== strpos($class, "@anonymous\0") ? get_parent_class($class).str_replace('.', '_', ContainerBuilder::hash($class)) : $class; $class = str_replace('\\', '_', $class).ucfirst($this->environment).($this->debug ? 'Debug' : '').'Container'; + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $class)) { throw new \InvalidArgumentException(sprintf('The environment "%s" contains invalid characters, it can only contain characters allowed in PHP class names.', $this->environment)); } diff --git a/Log/Logger.php b/Log/Logger.php index e3badc3676..1e6308d6e0 100644 --- a/Log/Logger.php +++ b/Log/Logger.php @@ -37,10 +37,10 @@ class Logger extends AbstractLogger private $formatter; private $handle; - public function __construct(string $minLevel = null, $output = 'php://stderr', callable $formatter = null) + public function __construct(string $minLevel = null, $output = null, callable $formatter = null) { if (null === $minLevel) { - $minLevel = 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::CRITICAL : LogLevel::WARNING; + $minLevel = null === $output || 'php://stdout' === $output || 'php://stderr' === $output ? LogLevel::ERROR : LogLevel::WARNING; if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : $_SERVER['SHELL_VERBOSITY'])) { @@ -58,7 +58,7 @@ public function __construct(string $minLevel = null, $output = 'php://stderr', c $this->minLevelIndex = self::$levels[$minLevel]; $this->formatter = $formatter ?: [$this, 'format']; - if (false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { + if ($output && false === $this->handle = \is_resource($output) ? $output : @fopen($output, 'a')) { throw new InvalidArgumentException(sprintf('Unable to open "%s".', $output)); } } @@ -79,10 +79,14 @@ public function log($level, $message, array $context = []) } $formatter = $this->formatter; - @fwrite($this->handle, $formatter($level, $message, $context)); + if ($this->handle) { + @fwrite($this->handle, $formatter($level, $message, $context)); + } else { + error_log($formatter($level, $message, $context, false)); + } } - private function format(string $level, string $message, array $context): string + private function format(string $level, string $message, array $context, bool $prefixDate = true): string { if (false !== strpos($message, '{')) { $replacements = []; @@ -101,6 +105,11 @@ private function format(string $level, string $message, array $context): string $message = strtr($message, $replacements); } - return sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message).PHP_EOL; + $log = sprintf('[%s] %s', $level, $message).PHP_EOL; + if ($prefixDate) { + $log = date(\DateTime::RFC3339).' '.$log; + } + + return $log; } } diff --git a/Tests/Controller/ArgumentResolverTest.php b/Tests/Controller/ArgumentResolverTest.php index d663297121..449bd1ae94 100644 --- a/Tests/Controller/ArgumentResolverTest.php +++ b/Tests/Controller/ArgumentResolverTest.php @@ -204,19 +204,19 @@ public function testGetNullableArguments() $request = Request::create('/'); $request->attributes->set('foo', 'foo'); $request->attributes->set('bar', new \stdClass()); - $request->attributes->set('mandatory', 'mandatory'); + $request->attributes->set('last', 'last'); $controller = [new NullableController(), 'action']; - $this->assertEquals(['foo', new \stdClass(), 'value', 'mandatory'], self::$resolver->getArguments($request, $controller)); + $this->assertEquals(['foo', new \stdClass(), 'value', 'last'], self::$resolver->getArguments($request, $controller)); } public function testGetNullableArgumentsWithDefaults() { $request = Request::create('/'); - $request->attributes->set('mandatory', 'mandatory'); + $request->attributes->set('last', 'last'); $controller = [new NullableController(), 'action']; - $this->assertEquals([null, null, 'value', 'mandatory'], self::$resolver->getArguments($request, $controller)); + $this->assertEquals([null, null, 'value', 'last'], self::$resolver->getArguments($request, $controller)); } public function testGetSessionArguments() diff --git a/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php b/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php index ba8ab56604..dfab909802 100644 --- a/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php +++ b/Tests/ControllerMetadata/ArgumentMetadataFactoryTest.php @@ -80,7 +80,7 @@ public function testSignature5() $this->assertEquals([ new ArgumentMetadata('foo', 'array', false, true, null, true), - new ArgumentMetadata('bar', null, false, false, null), + new ArgumentMetadata('bar', null, false, true, null, true), ], $arguments); } @@ -113,7 +113,7 @@ public function testNullableTypesSignature() new ArgumentMetadata('foo', 'string', false, false, null, true), new ArgumentMetadata('bar', \stdClass::class, false, false, null, true), new ArgumentMetadata('baz', 'string', false, true, 'value', true), - new ArgumentMetadata('mandatory', null, false, false, null, true), + new ArgumentMetadata('last', 'string', false, true, '', false), ], $arguments); } @@ -133,7 +133,7 @@ private function signature4($foo = 'default', $bar = 500, $baz = []) { } - private function signature5(array $foo = null, $bar) + private function signature5(array $foo = null, $bar = null) { } } diff --git a/Tests/Fixtures/Controller/NullableController.php b/Tests/Fixtures/Controller/NullableController.php index 9db4df7b4c..aacae0e3e3 100644 --- a/Tests/Fixtures/Controller/NullableController.php +++ b/Tests/Fixtures/Controller/NullableController.php @@ -13,7 +13,7 @@ class NullableController { - public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', $mandatory) + public function action(?string $foo, ?\stdClass $bar, ?string $baz = 'value', string $last = '') { } } diff --git a/Tests/HttpCache/StoreTest.php b/Tests/HttpCache/StoreTest.php index 6e56dbe0bb..b17cc0a44f 100644 --- a/Tests/HttpCache/StoreTest.php +++ b/Tests/HttpCache/StoreTest.php @@ -97,6 +97,27 @@ public function testSetsTheXContentDigestResponseHeaderBeforeStoring() $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); } + public function testDoesNotTrustXContentDigestFromUpstream() + { + $response = new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere']); + + $cacheKey = $this->store->write($this->request, $response); + $entries = $this->getStoreMetadata($cacheKey); + list(, $res) = $entries[0]; + + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $res['x-content-digest'][0]); + $this->assertEquals('en9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', $response->headers->get('X-Content-Digest')); + } + + public function testWritesResponseEvenIfXContentDigestIsPresent() + { + // Prime the store + $this->store->write($this->request, new Response('test', 200, ['X-Content-Digest' => 'untrusted-from-elsewhere'])); + + $response = $this->store->lookup($this->request); + $this->assertNotNull($response); + } + public function testFindsAStoredEntryWithLookup() { $this->storeSimpleEntry(); diff --git a/Tests/KernelTest.php b/Tests/KernelTest.php index ea6ab60ef5..f86c0ccd51 100644 --- a/Tests/KernelTest.php +++ b/Tests/KernelTest.php @@ -536,6 +536,27 @@ public function testKernelStartTimeIsResetWhileBootingAlreadyBootedKernel() $this->assertGreaterThan($preReBoot, $kernel->getStartTime()); } + public function testAnonymousKernelGeneratesValidContainerClass(): void + { + $kernel = new class('test', true) extends Kernel { + public function registerBundles(): iterable + { + return []; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + } + + public function getContainerClass(): string + { + return parent::getContainerClass(); + } + }; + + $this->assertRegExp('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*TestDebugContainer$/', $kernel->getContainerClass()); + } + /** * Returns a mock for the BundleInterface. */ diff --git a/composer.json b/composer.json index 2e8ab7af93..c016af2369 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", "symfony/error-handler": "^4.4|^5.0", "symfony/event-dispatcher": "^5.0",