Skip to content

Commit

Permalink
[Messenger] PhpSerializer: TypeError should throw MessageDecodingFail…
Browse files Browse the repository at this point in the history
…edException

Actually, the fix should handle more cases than only TypeError.
  • Loading branch information
B-Galati authored and nicolas-grekas committed Jan 30, 2024
1 parent 9b17d85 commit 3c1eda0
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 19 deletions.
18 changes: 18 additions & 0 deletions Tests/Fixtures/DummyMessageTyped.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Symfony\Component\Messenger\Tests\Fixtures;

class DummyMessageTyped implements DummyMessageInterface
{
private string $message;

public function __construct(string $message)
{
$this->message = $message;
}

public function getMessage(): string
{
return $this->message;
}
}
33 changes: 25 additions & 8 deletions Tests/Transport/Serialization/PhpSerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\Messenger\Exception\MessageDecodingFailedException;
use Symfony\Component\Messenger\Stamp\NonSendableStampInterface;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessageTyped;
use Symfony\Component\Messenger\Transport\Serialization\PhpSerializer;

class PhpSerializerTest extends TestCase
Expand All @@ -33,45 +34,45 @@ public function testEncodedIsDecodable()

public function testDecodingFailsWithMissingBodyKey()
{
$serializer = new PhpSerializer();

$this->expectException(MessageDecodingFailedException::class);
$this->expectExceptionMessage('Encoded envelope should have at least a "body", or maybe you should implement your own serializer');

$serializer = new PhpSerializer();

$serializer->decode([]);
}

public function testDecodingFailsWithBadFormat()
{
$serializer = new PhpSerializer();

$this->expectException(MessageDecodingFailedException::class);
$this->expectExceptionMessageMatches('/Could not decode/');

$serializer = new PhpSerializer();

$serializer->decode([
'body' => '{"message": "bar"}',
]);
}

public function testDecodingFailsWithBadBase64Body()
{
$serializer = new PhpSerializer();

$this->expectException(MessageDecodingFailedException::class);
$this->expectExceptionMessageMatches('/Could not decode/');

$serializer = new PhpSerializer();

$serializer->decode([
'body' => 'x',
]);
}

public function testDecodingFailsWithBadClass()
{
$serializer = new PhpSerializer();

$this->expectException(MessageDecodingFailedException::class);
$this->expectExceptionMessageMatches('/class "ReceivedSt0mp" not found/');

$serializer = new PhpSerializer();

$serializer->decode([
'body' => 'O:13:"ReceivedSt0mp":0:{}',
]);
Expand Down Expand Up @@ -99,6 +100,22 @@ public function testNonUtf8IsBase64Encoded()
$this->assertTrue((bool) preg_match('//u', $encoded['body']), 'Encodes non-UTF8 payloads');
$this->assertEquals($envelope, $serializer->decode($encoded));
}

/**
* @requires PHP 7.4
*/
public function testDecodingFailsForPropertyTypeMismatch()
{
$serializer = new PhpSerializer();
$encodedEnvelope = $serializer->encode(new Envelope(new DummyMessageTyped('true')));
// Simulate a change of property type in the code base
$encodedEnvelope['body'] = str_replace('s:4:\"true\"', 'b:1', $encodedEnvelope['body']);

$this->expectException(MessageDecodingFailedException::class);
$this->expectExceptionMessageMatches('/Could not decode/');

$serializer->decode($encodedEnvelope);
}
}

class DummyPhpSerializerNonSendableStamp implements NonSendableStampInterface
Expand Down
22 changes: 11 additions & 11 deletions Transport/Serialization/PhpSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
*/
class PhpSerializer implements SerializerInterface
{
/**
* {@inheritdoc}
*/
public function decode(array $encodedEnvelope): Envelope
{
if (empty($encodedEnvelope['body'])) {
Expand All @@ -38,9 +35,6 @@ public function decode(array $encodedEnvelope): Envelope
return $this->safelyUnserialize($serializeEnvelope);
}

/**
* {@inheritdoc}
*/
public function encode(Envelope $envelope): array
{
$envelope = $envelope->withoutStampsOfType(NonSendableStampInterface::class);
Expand All @@ -62,24 +56,30 @@ private function safelyUnserialize(string $contents)
throw new MessageDecodingFailedException('Could not decode an empty message using PHP serialization.');
}

$signalingException = new MessageDecodingFailedException(sprintf('Could not decode message using PHP serialization: %s.', $contents));
$prevUnserializeHandler = ini_set('unserialize_callback_func', self::class.'::handleUnserializeCallback');
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler, $signalingException) {
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$prevErrorHandler) {
if (__FILE__ === $file) {
throw $signalingException;
throw new \ErrorException($msg, 0, $type, $file, $line);
}

return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false;
});

try {
$meta = unserialize($contents);
/** @var Envelope */
$envelope = unserialize($contents);
} catch (\Throwable $e) {
if ($e instanceof MessageDecodingFailedException) {
throw $e;
}

throw new MessageDecodingFailedException('Could not decode Envelope: '.$e->getMessage(), 0, $e);
} finally {
restore_error_handler();
ini_set('unserialize_callback_func', $prevUnserializeHandler);
}

return $meta;
return $envelope;
}

/**
Expand Down

0 comments on commit 3c1eda0

Please sign in to comment.