diff --git a/app/code/Magento/Checkout/Block/Onepage.php b/app/code/Magento/Checkout/Block/Onepage.php index ca6b045ddbb5d..e01d5835b4cf0 100644 --- a/app/code/Magento/Checkout/Block/Onepage.php +++ b/app/code/Magento/Checkout/Block/Onepage.php @@ -38,7 +38,7 @@ class Onepage extends \Magento\Framework\View\Element\Template protected $layoutProcessors; /** - * @var \Magento\Framework\Serialize\Serializer\Json + * @var \Magento\Framework\Serialize\SerializerInterface */ private $serializer; @@ -48,8 +48,9 @@ class Onepage extends \Magento\Framework\View\Element\Template * @param \Magento\Checkout\Model\CompositeConfigProvider $configProvider * @param array $layoutProcessors * @param array $data - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer - * @throws \RuntimeException + * @param \Magento\Framework\Serialize\Serializer\Json $serializer + * @param \Magento\Framework\Serialize\SerializerInterface $serializerInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -57,7 +58,8 @@ public function __construct( \Magento\Checkout\Model\CompositeConfigProvider $configProvider, array $layoutProcessors = [], array $data = [], - \Magento\Framework\Serialize\Serializer\Json $serializer = null + \Magento\Framework\Serialize\Serializer\Json $serializer = null, + \Magento\Framework\Serialize\SerializerInterface $serializerInterface = null ) { parent::__construct($context, $data); $this->formKey = $formKey; @@ -65,12 +67,12 @@ public function __construct( $this->jsLayout = isset($data['jsLayout']) && is_array($data['jsLayout']) ? $data['jsLayout'] : []; $this->configProvider = $configProvider; $this->layoutProcessors = $layoutProcessors; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializer = $serializerInterface ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\JsonHexTag::class); } /** - * @return string + * @inheritdoc */ public function getJsLayout() { @@ -78,7 +80,7 @@ public function getJsLayout() $this->jsLayout = $processor->process($this->jsLayout); } - return json_encode($this->jsLayout, JSON_HEX_TAG); + return $this->serializer->serialize($this->jsLayout); } /** @@ -115,11 +117,13 @@ public function getBaseUrl() } /** + * Retrieve serialized checkout config. + * * @return bool|string * @since 100.2.0 */ public function getSerializedCheckoutConfig() { - return json_encode($this->getCheckoutConfig(), JSON_HEX_TAG); + return $this->serializer->serialize($this->getCheckoutConfig()); } } diff --git a/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php b/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php index 54f77c95148ac..b54339aa2c1d8 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php @@ -35,7 +35,7 @@ class OnepageTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $serializer; + private $serializerMock; protected function setUp() { @@ -49,7 +49,7 @@ protected function setUp() \Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class ); - $this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializerMock = $this->createMock(\Magento\Framework\Serialize\Serializer\JsonHexTag::class); $this->model = new \Magento\Checkout\Block\Onepage( $contextMock, @@ -57,7 +57,8 @@ protected function setUp() $this->configProviderMock, [$this->layoutProcessorMock], [], - $this->serializer + $this->serializerMock, + $this->serializerMock ); } @@ -93,6 +94,7 @@ public function testGetJsLayout() $processedLayout = ['layout' => ['processed' => true]]; $jsonLayout = '{"layout":{"processed":true}}'; $this->layoutProcessorMock->expects($this->once())->method('process')->with([])->willReturn($processedLayout); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn($jsonLayout); $this->assertEquals($jsonLayout, $this->model->getJsLayout()); } @@ -101,6 +103,7 @@ public function testGetSerializedCheckoutConfig() { $checkoutConfig = ['checkout', 'config']; $this->configProviderMock->expects($this->once())->method('getConfig')->willReturn($checkoutConfig); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn(json_encode($checkoutConfig)); $this->assertEquals(json_encode($checkoutConfig), $this->model->getSerializedCheckoutConfig()); } diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml index d80f88786c87b..00bcd2a27005a 100644 --- a/app/code/Magento/Checkout/etc/frontend/di.xml +++ b/app/code/Magento/Checkout/etc/frontend/di.xml @@ -59,6 +59,7 @@ Magento\Checkout\Block\Checkout\TotalsProcessor Magento\Checkout\Block\Checkout\DirectoryDataProcessor + Magento\Framework\Serialize\Serializer\JsonHexTag diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index e249a64861d43..9304d25cc8a98 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -5,6 +5,8 @@ */ namespace Magento\Ui\TemplateEngine\Xhtml; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\JsonHexTag; use Magento\Framework\View\Layout\Generator\Structure; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\View\TemplateEngine\Xhtml\Template; @@ -42,25 +44,33 @@ class Result implements ResultInterface */ protected $logger; + /** + * @var JsonHexTag + */ + private $jsonSerializer; + /** * @param Template $template * @param CompilerInterface $compiler * @param UiComponentInterface $component * @param Structure $structure * @param LoggerInterface $logger + * @param JsonHexTag $jsonSerializer */ public function __construct( Template $template, CompilerInterface $compiler, UiComponentInterface $component, Structure $structure, - LoggerInterface $logger + LoggerInterface $logger, + JsonHexTag $jsonSerializer = null ) { $this->template = $template; $this->compiler = $compiler; $this->component = $component; $this->structure = $structure; $this->logger = $logger; + $this->jsonSerializer = $jsonSerializer ?? ObjectManager::getInstance()->get(JsonHexTag::class); } /** @@ -81,7 +91,7 @@ public function getDocumentElement() public function appendLayoutConfiguration() { $layoutConfiguration = $this->wrapContent( - json_encode($this->structure->generate($this->component), JSON_HEX_TAG) + $this->jsonSerializer->serialize($this->structure->generate($this->component)) ); $this->template->append($layoutConfiguration); } diff --git a/lib/internal/Magento/Framework/Serialize/README.md b/lib/internal/Magento/Framework/Serialize/README.md index 5af8fb7f71b6b..d900f89208a54 100644 --- a/lib/internal/Magento/Framework/Serialize/README.md +++ b/lib/internal/Magento/Framework/Serialize/README.md @@ -3,6 +3,7 @@ **Serialize** library provides interface *SerializerInterface* and multiple implementations: * *Json* - default implementation. Uses PHP native json_encode/json_decode functions; + * *JsonHexTag* - default implementation. Uses PHP native json_encode/json_decode functions with `JSON_HEX_TAG` option enabled; * *Serialize* - less secure than *Json*, but gives higher performance on big arrays. Uses PHP native serialize/unserialize functions, does not unserialize objects on PHP 7. Using *Serialize* implementation directly is discouraged, always use *SerializerInterface*, using *Serialize* implementation may lead to security vulnerabilities. \ No newline at end of file diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php b/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php new file mode 100644 index 0000000000000..4a5406ff3fd99 --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php @@ -0,0 +1,35 @@ + are converted to \u003C and \u003E), + * unserialize JSON encoded data + * + * @api + * @since 100.2.0 + */ +class JsonHexTag extends Json implements SerializerInterface +{ + /** + * @inheritDoc + * @since 100.2.0 + */ + public function serialize($data): string + { + $result = json_encode($data, JSON_HEX_TAG); + if (false === $result) { + throw new \InvalidArgumentException('Unable to serialize value.'); + } + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php new file mode 100644 index 0000000000000..c867dced0fc6e --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php @@ -0,0 +1,118 @@ +json = $objectManager->getObject(JsonHexTag::class); + } + + /** + * @param string|int|float|bool|array|null $value + * @param string $expected + * @dataProvider serializeDataProvider + */ + public function testSerialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->serialize($value) + ); + } + + public function serializeDataProvider() + { + $dataObject = new DataObject(['something']); + return [ + ['', '""'], + ['string', '"string"'], + [null, 'null'], + [false, 'false'], + [['a' => 'b', 'd' => 123], '{"a":"b","d":123}'], + [123, '123'], + [10.56, '10.56'], + [$dataObject, '{}'], + ['< >', '"\u003C \u003E"'], + ]; + } + + /** + * @param string $value + * @param string|int|float|bool|array|null $expected + * @dataProvider unserializeDataProvider + */ + public function testUnserialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->unserialize($value) + ); + } + + /** + * @return array + */ + public function unserializeDataProvider(): array + { + return [ + ['""', ''], + ['"string"', 'string'], + ['null', null], + ['false', false], + ['{"a":"b","d":123}', ['a' => 'b', 'd' => 123]], + ['123', 123], + ['10.56', 10.56], + ['{}', []], + ['"\u003C \u003E"', '< >'], + ]; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to serialize value. + */ + public function testSerializeException() + { + $this->json->serialize(STDOUT); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to unserialize value. + * @dataProvider unserializeExceptionDataProvider + */ + public function testUnserializeException($value) + { + $this->json->unserialize($value); + } + + /** + * @return array + */ + public function unserializeExceptionDataProvider(): array + { + return [ + [''], + [false], + [null], + ['{'] + ]; + } +}