From 07402f4af3a6b8eba7c48edf2a09bc715e1efe75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 12 Dec 2020 17:30:27 +0100 Subject: [PATCH 01/11] Dont allow unserializing classes with a destructor - 5.1 --- .../Component/HttpClient/Response/AmpResponse.php | 10 ++++++++++ .../Bridge/AmazonSqs/Transport/Connection.php | 10 ++++++++++ src/Symfony/Component/String/UnicodeString.php | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/src/Symfony/Component/HttpClient/Response/AmpResponse.php b/src/Symfony/Component/HttpClient/Response/AmpResponse.php index 7f6c3c208f..46dffe118d 100644 --- a/src/Symfony/Component/HttpClient/Response/AmpResponse.php +++ b/src/Symfony/Component/HttpClient/Response/AmpResponse.php @@ -109,6 +109,16 @@ final class AmpResponse implements ResponseInterface return null !== $type ? $this->info[$type] ?? null : $this->info; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { try { diff --git a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php index 96f8d17eb2..83102668ae 100644 --- a/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/AmazonSqs/Transport/Connection.php @@ -63,6 +63,16 @@ class Connection $this->client = $client ?? new SqsClient([]); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->reset(); diff --git a/src/Symfony/Component/String/UnicodeString.php b/src/Symfony/Component/String/UnicodeString.php index 2db507d7bb..9b906c6fc2 100644 --- a/src/Symfony/Component/String/UnicodeString.php +++ b/src/Symfony/Component/String/UnicodeString.php @@ -359,6 +359,10 @@ class UnicodeString extends AbstractUnicodeString public function __wakeup() { + if (!\is_string($this->string)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string); } From 98601908bb727a3b40433da8b7500d392ff7ad24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 15 Dec 2020 11:45:32 +0100 Subject: [PATCH 02/11] Dont allow unserializing classes with a destructor - 5.2 --- .../HttpClient/Response/CommonResponseTrait.php | 10 ++++++++++ .../HttpClient/Response/TraceableResponse.php | 10 ++++++++++ .../Component/RateLimiter/Policy/TokenBucket.php | 4 ++++ 3 files changed, 24 insertions(+) diff --git a/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php b/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php index 8ad619adce..69c0fa94e3 100644 --- a/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/CommonResponseTrait.php @@ -127,6 +127,16 @@ trait CommonResponseTrait return $stream; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + /** * Closes the response and all its network handles. */ diff --git a/src/Symfony/Component/HttpClient/Response/TraceableResponse.php b/src/Symfony/Component/HttpClient/Response/TraceableResponse.php index 9e1a2d5e01..32ea27e2c2 100644 --- a/src/Symfony/Component/HttpClient/Response/TraceableResponse.php +++ b/src/Symfony/Component/HttpClient/Response/TraceableResponse.php @@ -44,6 +44,16 @@ class TraceableResponse implements ResponseInterface, StreamableInterface $this->event = $event; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { try { diff --git a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php index 8464acf149..e6dd30a52f 100644 --- a/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php +++ b/src/Symfony/Component/RateLimiter/Policy/TokenBucket.php @@ -104,6 +104,10 @@ final class TokenBucket implements LimiterStateInterface */ public function __wakeup(): void { + if (!\is_string($this->stringRate)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + $this->rate = Rate::fromString($this->stringRate); unset($this->stringRate); } From da5c39ec2e65ed4e58b08577e8de29e06ef65a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 8 Dec 2020 18:02:24 +0100 Subject: [PATCH 03/11] Move AuthenticationSuccessEvent outside try/catch block --- .../Authentication/AuthenticatorManager.php | 24 +++++++++---------- .../EventListener/UserCheckerListener.php | 13 +++++----- .../EventListener/UserCheckerListenerTest.php | 18 ++++++-------- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index 318fd7bd21..d3afaacdd1 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -185,18 +185,6 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent if (null !== $this->logger) { $this->logger->info('Authenticator successful!', ['token' => $authenticatedToken, 'authenticator' => \get_class($authenticator)]); } - - // success! (sets the token on the token storage, etc) - $response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator); - if ($response instanceof Response) { - return $response; - } - - if (null !== $this->logger) { - $this->logger->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator)]); - } - - return null; } catch (AuthenticationException $e) { // oh no! Authentication failed! $response = $this->handleAuthenticationFailure($e, $request, $authenticator, $passport); @@ -206,6 +194,18 @@ class AuthenticatorManager implements AuthenticatorManagerInterface, UserAuthent return null; } + + // success! (sets the token on the token storage, etc) + $response = $this->handleAuthenticationSuccess($authenticatedToken, $passport, $request, $authenticator); + if ($response instanceof Response) { + return $response; + } + + if (null !== $this->logger) { + $this->logger->debug('Authenticator set no success response: request continues.', ['authenticator' => \get_class($authenticator)]); + } + + return null; } private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator): ?Response diff --git a/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php b/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php index 62da75e91b..bc346b9ad8 100644 --- a/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/UserCheckerListener.php @@ -12,11 +12,12 @@ namespace Symfony\Component\Security\Http\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticatedUserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; -use Symfony\Component\Security\Http\Event\LoginSuccessEvent; /** * @author Wouter de Jong @@ -43,21 +44,21 @@ class UserCheckerListener implements EventSubscriberInterface $this->userChecker->checkPreAuth($passport->getUser()); } - public function postCheckCredentials(LoginSuccessEvent $event): void + public function postCheckCredentials(AuthenticationSuccessEvent $event): void { - $passport = $event->getPassport(); - if (!$passport instanceof UserPassportInterface || null === $passport->getUser()) { + $user = $event->getAuthenticationToken()->getUser(); + if (!$user instanceof UserInterface) { return; } - $this->userChecker->checkPostAuth($passport->getUser()); + $this->userChecker->checkPostAuth($user); } public static function getSubscribedEvents(): array { return [ CheckPassportEvent::class => ['preCheckCredentials', 256], - LoginSuccessEvent::class => ['postCheckCredentials', 256], + AuthenticationSuccessEvent::class => ['postCheckCredentials', 256], ]; } } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php index 5422abfe5d..213cfbba68 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/UserCheckerListenerTest.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Security\Http\Tests\EventListener; use PHPUnit\Framework\TestCase; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; +use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; @@ -21,8 +21,8 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PreAuthenticate use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; +use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; use Symfony\Component\Security\Http\Event\CheckPassportEvent; -use Symfony\Component\Security\Http\Event\LoginSuccessEvent; use Symfony\Component\Security\Http\EventListener\UserCheckerListener; class UserCheckerListenerTest extends TestCase @@ -63,14 +63,14 @@ class UserCheckerListenerTest extends TestCase { $this->userChecker->expects($this->once())->method('checkPostAuth')->with($this->user); - $this->listener->postCheckCredentials($this->createLoginSuccessEvent()); + $this->listener->postCheckCredentials(new AuthenticationSuccessEvent(new PostAuthenticationToken($this->user, 'main', []))); } public function testPostAuthNoUser() { $this->userChecker->expects($this->never())->method('checkPostAuth'); - $this->listener->postCheckCredentials($this->createLoginSuccessEvent($this->createMock(PassportInterface::class))); + $this->listener->postCheckCredentials(new AuthenticationSuccessEvent(new PreAuthenticatedToken('nobody', null, 'main'))); } private function createCheckPassportEvent($passport = null) @@ -82,12 +82,8 @@ class UserCheckerListenerTest extends TestCase return new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport); } - private function createLoginSuccessEvent($passport = null) + private function createAuthenticationSuccessEvent() { - if (null === $passport) { - $passport = new SelfValidatingPassport(new UserBadge('test', function () { return $this->user; })); - } - - return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $this->createMock(TokenInterface::class), new Request(), null, 'main'); + return new AuthenticationSuccessEvent(new PostAuthenticationToken($this->user, 'main', [])); } } From 1cfc76301846afadea663f55a8f3bd9d03e7b4fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 7 Jan 2021 00:10:20 +0100 Subject: [PATCH 04/11] Fix missing BCC recipients in SES bridge --- .../Amazon/Tests/Transport/SesHttpTransportTest.php | 7 +++++++ .../Bridge/Amazon/Transport/SesHttpTransport.php | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php index c57d00469d..cc450e5e01 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Tests/Transport/SesHttpTransportTest.php @@ -60,6 +60,12 @@ class SesHttpTransportTest extends TestCase $this->assertStringContainsString('AWS3-HTTPS AWSAccessKeyId=ACCESS_KEY,Algorithm=HmacSHA256,Signature=', $options['headers'][0] ?? $options['request_headers'][0]); parse_str($options['body'], $body); + + $this->assertArrayHasKey('Destinations_member_1', $body); + $this->assertSame('saif.gmati@symfony.com', $body['Destinations_member_1']); + $this->assertArrayHasKey('Destinations_member_2', $body); + $this->assertSame('jeremy@derusse.com', $body['Destinations_member_2']); + $content = base64_decode($body['RawMessage_Data']); $this->assertStringContainsString('Hello!', $content); @@ -83,6 +89,7 @@ class SesHttpTransportTest extends TestCase $mail = new Email(); $mail->subject('Hello!') ->to(new Address('saif.gmati@symfony.com', 'Saif Eddin')) + ->bcc(new Address('jeremy@derusse.com', 'Jérémy Derussé')) ->from(new Address('fabpot@symfony.com', 'Fabien')) ->text('Hello There!'); diff --git a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php index 8d49c7e6ea..d17d23b1e0 100644 --- a/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Amazon/Transport/SesHttpTransport.php @@ -52,7 +52,7 @@ class SesHttpTransport extends AbstractHttpTransport $date = gmdate('D, d M Y H:i:s e'); $auth = sprintf('AWS3-HTTPS AWSAccessKeyId=%s,Algorithm=HmacSHA256,Signature=%s', $this->accessKey, $this->getSignature($date)); - $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), [ + $request = [ 'headers' => [ 'X-Amzn-Authorization' => $auth, 'Date' => $date, @@ -61,7 +61,13 @@ class SesHttpTransport extends AbstractHttpTransport 'Action' => 'SendRawEmail', 'RawMessage.Data' => base64_encode($message->toString()), ], - ]); + ]; + $index = 1; + foreach ($message->getEnvelope()->getRecipients() as $recipient) { + $request['body']['Destinations.member.'.$index++] = $recipient->getAddress(); + } + + $response = $this->client->request('POST', 'https://'.$this->getEndpoint(), $request); $result = new \SimpleXMLElement($response->getContent(false)); if (200 !== $response->getStatusCode()) { From 70fe66005a6c04ec8e31758863b62fc63706ea32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 7 Jan 2021 23:51:21 +0100 Subject: [PATCH 05/11] Fix transient test with HttpClient jitter --- .../Tests/Retry/GenericRetryStrategyTest.php | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php b/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php index e04cdb45b6..98b6578f0b 100644 --- a/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php +++ b/src/Symfony/Component/HttpClient/Tests/Retry/GenericRetryStrategyTest.php @@ -93,19 +93,16 @@ class GenericRetryStrategyTest extends TestCase public function testJitter() { $strategy = new GenericRetryStrategy([], 1000, 1, 0, 1); - $belowHalf = 0; - $aboveHalf = 0; - for ($i = 0; $i < 20; ++$i) { + $min = 2000; + $max = 0; + for ($i = 0; $i < 50; ++$i) { $delay = $strategy->getDelay($this->getContext(0, 'GET', 'http://example.com/', 200), null, null); - if ($delay < 500) { - ++$belowHalf; - } elseif ($delay > 1500) { - ++$aboveHalf; - } + $min = min($min, $delay); + $max = max($max, $delay); } - - $this->assertGreaterThanOrEqual(1, $belowHalf); - $this->assertGreaterThanOrEqual(1, $aboveHalf); + $this->assertGreaterThanOrEqual(1000, $max - $min); + $this->assertGreaterThanOrEqual(1000, $max); + $this->assertLessThanOrEqual(1000, $min); } private function getContext($retryCount, $method, $url, $statusCode): AsyncContext From a2ad4fa8b391a117d34a2902336c4fc09b39941d Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 8 Jan 2021 10:29:29 +0100 Subject: [PATCH 06/11] fix handling float-like key attribute values --- .../Config/Definition/PrototypedArrayNode.php | 4 +++ .../Tests/Definition/NormalizationTest.php | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php index 7f52b57997..68bb270172 100644 --- a/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php +++ b/src/Symfony/Component/Config/Definition/PrototypedArrayNode.php @@ -237,6 +237,10 @@ class PrototypedArrayNode extends ArrayNode } elseif (isset($v[$this->keyAttribute])) { $k = $v[$this->keyAttribute]; + if (\is_float($k)) { + $k = var_export($k, true); + } + // remove the key attribute when required if ($this->removeKeyAttribute) { unset($v[$this->keyAttribute]); diff --git a/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php b/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php index 931cf987ea..948ca275c6 100644 --- a/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php +++ b/src/Symfony/Component/Config/Tests/Definition/NormalizationTest.php @@ -201,6 +201,32 @@ class NormalizationTest extends TestCase $this->assertNormalized($tree, $data, $data); } + public function testFloatLikeValueAsMapKeyAttribute() + { + $tree = (new TreeBuilder('root')) + ->getRootNode() + ->useAttributeAsKey('number') + ->arrayPrototype() + ->children() + ->scalarNode('foo')->end() + ->end() + ->end() + ->end() + ->buildTree() + ; + + $this->assertNormalized($tree, [ + [ + 'number' => 3.0, + 'foo' => 'bar', + ], + ], [ + '3.0' => [ + 'foo' => 'bar', + ], + ]); + } + public static function assertNormalized(NodeInterface $tree, $denormalized, $normalized) { self::assertSame($normalized, $tree->normalize($denormalized)); From aa7f83576cb0d2f3933fe615c3a9df763e7b8bb4 Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Sun, 10 Jan 2021 20:12:53 +0100 Subject: [PATCH 07/11] Contracts: Remove ellipsis --- src/Symfony/Contracts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Contracts/README.md b/src/Symfony/Contracts/README.md index 480c2a90e7..5beb497d7e 100644 --- a/src/Symfony/Contracts/README.md +++ b/src/Symfony/Contracts/README.md @@ -11,7 +11,7 @@ Design Principles * contracts are split by domain, each into their own sub-namespaces; * contracts are small and consistent sets of PHP interfaces, traits, normative - docblocks and reference test suites when applicable, ...; + docblocks and reference test suites when applicable; * all contracts must have a proven implementation to enter this repository; * they must be backward compatible with existing Symfony components. From f72c6a5ad40ea5feac5a6d89946c165e95d8f538 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 11 Jan 2021 14:44:09 +0100 Subject: [PATCH 08/11] a colon followed by spaces exclusively separates mapping keys and values --- src/Symfony/Component/Yaml/Parser.php | 2 +- src/Symfony/Component/Yaml/Tests/InlineTest.php | 17 +++++++++++++++++ src/Symfony/Component/Yaml/Tests/ParserTest.php | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 8ca26e0e8f..6d0d523265 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -200,7 +200,7 @@ class Parser array_pop($this->refsBeingParsed); } } elseif ( - self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P.+))?$#u', rtrim($this->currentLine), $values) + self::preg_match('#^(?P(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:( ++(?P.+))?$#u', rtrim($this->currentLine), $values) && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"])) ) { if ($context && 'sequence' == $context) { diff --git a/src/Symfony/Component/Yaml/Tests/InlineTest.php b/src/Symfony/Component/Yaml/Tests/InlineTest.php index 338ea504a8..66eae463db 100644 --- a/src/Symfony/Component/Yaml/Tests/InlineTest.php +++ b/src/Symfony/Component/Yaml/Tests/InlineTest.php @@ -877,4 +877,21 @@ class InlineTest extends TestCase [['!'], '! ["!"]'], ]; } + + /** + * @dataProvider ideographicSpaceProvider + */ + public function testParseIdeographicSpace(string $yaml, string $expected) + { + $this->assertSame($expected, Inline::parse($yaml)); + } + + public function ideographicSpaceProvider(): array + { + return [ + ["\u{3000}", ' '], + ["'\u{3000}'", ' '], + ["'a b'", 'a b'], + ]; + } } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 1db540369d..1fa448dad5 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -2733,6 +2733,22 @@ YAML; // (before, there was no \n after row2) $this->assertSame(['a' => ['b' => "row\nrow2\n"], 'c' => 'd'], $this->parser->parse($longDocument)); } + + public function testParseIdeographicSpaces() + { + $expected = <<assertSame([ + 'unquoted' => ' ', + 'quoted' => ' ', + 'within_string' => 'a b', + 'regular_space' => 'a b', + ], $this->parser->parse($expected)); + } } class B From 340d15e4000e24414b3e3430b54e737834a0f406 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 11 Jan 2021 19:27:14 +0100 Subject: [PATCH 09/11] [Cache] fix possible collision when writing tmp file in filesystem adapter --- .../Cache/Traits/FilesystemCommonTrait.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php index 5509e21028..fe61f08c16 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemCommonTrait.php @@ -93,9 +93,20 @@ trait FilesystemCommonTrait set_error_handler(__CLASS__.'::throwError'); try { if (null === $this->tmp) { - $this->tmp = $this->directory.uniqid('', true); + $this->tmp = $this->directory.bin2hex(random_bytes(6)); } - file_put_contents($this->tmp, $data); + try { + $h = fopen($this->tmp, 'x'); + } catch (\ErrorException $e) { + if (false === strpos($e->getMessage(), 'File exists')) { + throw $e; + } + + $this->tmp = $this->directory.bin2hex(random_bytes(6)); + $h = fopen($this->tmp, 'x'); + } + fwrite($h, $data); + fclose($h); if (null !== $expiresAt) { touch($this->tmp, $expiresAt); From 955395c99976dda9da6a211838b19adc663bdb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 12 Dec 2020 17:18:38 +0100 Subject: [PATCH 10/11] Dont allow unserializing classes with a destructor - 4.4 --- .../Monolog/Handler/ElasticsearchLogstashHandler.php | 10 ++++++++++ src/Symfony/Component/ErrorHandler/BufferingLogger.php | 10 ++++++++++ src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php | 10 ++++++++++ src/Symfony/Component/HttpClient/CurlHttpClient.php | 10 ++++++++++ src/Symfony/Component/HttpClient/HttplugClient.php | 10 ++++++++++ .../Component/HttpClient/Response/ResponseTrait.php | 10 ++++++++++ .../Component/Mailer/Transport/Smtp/SmtpTransport.php | 10 ++++++++++ src/Symfony/Component/Mime/Part/DataPart.php | 6 ++++++ 8 files changed, 76 insertions(+) diff --git a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php index 612350200e..c31dba8802 100644 --- a/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php +++ b/src/Symfony/Bridge/Monolog/Handler/ElasticsearchLogstashHandler.php @@ -129,6 +129,16 @@ class ElasticsearchLogstashHandler extends AbstractHandler $this->wait(false); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->wait(true); diff --git a/src/Symfony/Component/ErrorHandler/BufferingLogger.php b/src/Symfony/Component/ErrorHandler/BufferingLogger.php index 16e433dedf..72be64d127 100644 --- a/src/Symfony/Component/ErrorHandler/BufferingLogger.php +++ b/src/Symfony/Component/ErrorHandler/BufferingLogger.php @@ -35,6 +35,16 @@ class BufferingLogger extends AbstractLogger return $logs; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { foreach ($this->logs as [$level, $message, $context]) { diff --git a/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php b/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php index c3df62ce32..5e2ba0a697 100644 --- a/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php +++ b/src/Symfony/Component/HttpClient/Chunk/ErrorChunk.php @@ -115,6 +115,16 @@ class ErrorChunk implements ChunkInterface return $this->didThrow; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { if (!$this->didThrow) { diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index e69761b392..766cf222c2 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -362,6 +362,16 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, } } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->reset(); diff --git a/src/Symfony/Component/HttpClient/HttplugClient.php b/src/Symfony/Component/HttpClient/HttplugClient.php index 5d691e024d..a825c5d078 100644 --- a/src/Symfony/Component/HttpClient/HttplugClient.php +++ b/src/Symfony/Component/HttpClient/HttplugClient.php @@ -218,6 +218,16 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__)); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->wait(); diff --git a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php index d6ddae6def..69caabf4b8 100644 --- a/src/Symfony/Component/HttpClient/Response/ResponseTrait.php +++ b/src/Symfony/Component/HttpClient/Response/ResponseTrait.php @@ -199,6 +199,16 @@ trait ResponseTrait return $stream; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + /** * Closes the response and all its network handles. */ diff --git a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php index 5ef8a88255..c924b5e93b 100644 --- a/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php +++ b/src/Symfony/Component/Mailer/Transport/Smtp/SmtpTransport.php @@ -331,6 +331,16 @@ class SmtpTransport extends AbstractTransport $this->restartCounter = 0; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->stop(); diff --git a/src/Symfony/Component/Mime/Part/DataPart.php b/src/Symfony/Component/Mime/Part/DataPart.php index 213f3c10c8..0da9230c29 100644 --- a/src/Symfony/Component/Mime/Part/DataPart.php +++ b/src/Symfony/Component/Mime/Part/DataPart.php @@ -155,7 +155,13 @@ class DataPart extends TextPart $r->setValue($this, $this->_headers); unset($this->_headers); + if (!\is_array($this->_parent)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) { + if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name])) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } $r = new \ReflectionProperty(TextPart::class, $name); $r->setAccessible(true); $r->setValue($this, $this->_parent[$name]); From facc095944a74efcbe0c69603c30087abf8e8a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 12 Dec 2020 16:46:18 +0100 Subject: [PATCH 11/11] Dont allow unserializing classes with a destructor --- .../FrameworkBundle/Tests/Functional/app/AppKernel.php | 6 ++++++ .../Loader/Configurator/AbstractConfigurator.php | 10 ++++++++++ .../Component/Form/Util/OrderedHashMapIterator.php | 10 ++++++++++ .../HttpKernel/DataCollector/DataCollector.php | 4 ++++ .../HttpKernel/DataCollector/DumpDataCollector.php | 2 +- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++++ .../Component/Ldap/Adapter/ExtLdap/Connection.php | 10 ++++++++++ src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php | 10 ++++++++++ src/Symfony/Component/Lock/Lock.php | 10 ++++++++++ src/Symfony/Component/Process/Pipes/UnixPipes.php | 10 ++++++++++ src/Symfony/Component/Process/Pipes/WindowsPipes.php | 10 ++++++++++ src/Symfony/Component/Process/Process.php | 10 ++++++++++ .../Loader/Configurator/CollectionConfigurator.php | 10 ++++++++++ .../Routing/Loader/Configurator/ImportConfigurator.php | 10 ++++++++++ 14 files changed, 115 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php index c6675c3b1a..94b9bfa012 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/AppKernel.php @@ -87,6 +87,12 @@ class AppKernel extends Kernel public function __wakeup() { + foreach ($this as $k => $v) { + if (\is_object($v)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + } + $this->__construct($this->varDir, $this->testCase, $this->rootConfig, $this->environment, $this->debug); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php index 2b7aa3fc81..b3020fb93f 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/AbstractConfigurator.php @@ -34,6 +34,16 @@ abstract class AbstractConfigurator throw new \BadMethodCallException(sprintf('Call to undefined method "%s::%s()".', static::class, $method)); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + /** * Checks that a value is valid, optionally replacing Definition and Reference configurators by their configure value. * diff --git a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php index 323fdd2329..d1e03e8292 100644 --- a/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php +++ b/src/Symfony/Component/Form/Util/OrderedHashMapIterator.php @@ -76,6 +76,16 @@ class OrderedHashMapIterator implements \Iterator $this->managedCursors[$this->cursorId] = &$this->cursor; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + /** * Removes the iterator's cursors from the managed cursors of the * corresponding {@link OrderedHashMap} instance. diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php index 832a5d9ebf..3938dab6a1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php @@ -123,6 +123,10 @@ abstract class DataCollector implements DataCollectorInterface public function __wakeup() { if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'unserialize'))->getDeclaringClass()->name) { + if (\is_object($this->data)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + @trigger_error(sprintf('Implementing the "%s::unserialize()" method is deprecated since Symfony 4.3, store all the serialized state in the "data" property instead.', $c), \E_USER_DEPRECATED); $this->unserialize($this->data); } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index df4ec5f670..af8b3a9458 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -184,7 +184,7 @@ class DumpDataCollector extends DataCollector implements DataDumperInterface $fileLinkFormat = array_pop($this->data); $this->dataCount = \count($this->data); - self::__construct($this->stopwatch, $fileLinkFormat, $charset); + self::__construct($this->stopwatch, \is_string($fileLinkFormat) || $fileLinkFormat instanceof FileLinkFormatter ? $fileLinkFormat : null, \is_string($charset) ? $charset : null); } public function getDumpsCount() diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index b91fc66a9f..c9317c81ca 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -920,6 +920,10 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl public function __wakeup() { + if (\is_object($this->environment) || \is_object($this->debug)) { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + if (__CLASS__ !== $c = (new \ReflectionMethod($this, 'serialize'))->getDeclaringClass()->name) { @trigger_error(sprintf('Implementing the "%s::serialize()" method is deprecated since Symfony 4.3.', $c), \E_USER_DEPRECATED); $this->unserialize($this->serialized); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php index 2b224fe72b..69ad4dd9b6 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php @@ -35,6 +35,16 @@ class Connection extends AbstractConnection /** @var resource */ private $connection; + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->disconnect(); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php index e3a6fc8d65..91b758e9ac 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php @@ -38,6 +38,16 @@ class Query extends AbstractQuery parent::__construct($connection, $dn, $query, $options); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $con = $this->connection->getResource(); diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index 1271100550..c9c12b65d8 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -50,6 +50,16 @@ final class Lock implements LockInterface, LoggerAwareInterface $this->logger = new NullLogger(); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + /** * Automatically releases the underlying lock when the object is destructed. */ diff --git a/src/Symfony/Component/Process/Pipes/UnixPipes.php b/src/Symfony/Component/Process/Pipes/UnixPipes.php index 70fdd29574..7cb5bab76e 100644 --- a/src/Symfony/Component/Process/Pipes/UnixPipes.php +++ b/src/Symfony/Component/Process/Pipes/UnixPipes.php @@ -35,6 +35,16 @@ class UnixPipes extends AbstractPipes parent::__construct($input); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->close(); diff --git a/src/Symfony/Component/Process/Pipes/WindowsPipes.php b/src/Symfony/Component/Process/Pipes/WindowsPipes.php index b22171dd4a..b2c3f4f4f3 100644 --- a/src/Symfony/Component/Process/Pipes/WindowsPipes.php +++ b/src/Symfony/Component/Process/Pipes/WindowsPipes.php @@ -88,6 +88,16 @@ class WindowsPipes extends AbstractPipes parent::__construct($input); } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->close(); diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index e38e144d44..244d0c0457 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -198,6 +198,16 @@ class Process implements \IteratorAggregate return $process; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->stop(0); diff --git a/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php index 79c1100a82..c0a074c0b7 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/CollectionConfigurator.php @@ -36,6 +36,16 @@ class CollectionConfigurator $this->parentPrefixes = $parentPrefixes; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { if (null === $this->prefixes) { diff --git a/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php index 0059a632a1..b950d4f47a 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/ImportConfigurator.php @@ -30,6 +30,16 @@ class ImportConfigurator $this->route = $route; } + public function __sleep() + { + throw new \BadMethodCallException('Cannot serialize '.__CLASS__); + } + + public function __wakeup() + { + throw new \BadMethodCallException('Cannot unserialize '.__CLASS__); + } + public function __destruct() { $this->parent->addCollection($this->route);