Skip to content

Commit d3359c4

Browse files
ENGCOM-3858: [Backport] Use the new json serializer which throws an error when failing #20271
- Merge Pull Request #20271 from quisse/magento2:2.2-develop-PR-port-16154 - Merged commits: 1. 7e3ea1b 2. f72fb42 3. dbb905c 4. c3f4d5d 5. 31bf4b2
2 parents 0369e3a + 31bf4b2 commit d3359c4

File tree

8 files changed

+200
-14
lines changed

8 files changed

+200
-14
lines changed

app/code/Magento/Checkout/Block/Onepage.php

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class Onepage extends \Magento\Framework\View\Element\Template
3838
protected $layoutProcessors;
3939

4040
/**
41-
* @var \Magento\Framework\Serialize\Serializer\Json
41+
* @var \Magento\Framework\Serialize\SerializerInterface
4242
*/
4343
private $serializer;
4444

@@ -48,37 +48,39 @@ class Onepage extends \Magento\Framework\View\Element\Template
4848
* @param \Magento\Checkout\Model\CompositeConfigProvider $configProvider
4949
* @param array $layoutProcessors
5050
* @param array $data
51-
* @param \Magento\Framework\Serialize\Serializer\Json|null $serializer
52-
* @throws \RuntimeException
51+
* @param \Magento\Framework\Serialize\Serializer\Json $serializer
52+
* @param \Magento\Framework\Serialize\SerializerInterface $serializerInterface
53+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5354
*/
5455
public function __construct(
5556
\Magento\Framework\View\Element\Template\Context $context,
5657
\Magento\Framework\Data\Form\FormKey $formKey,
5758
\Magento\Checkout\Model\CompositeConfigProvider $configProvider,
5859
array $layoutProcessors = [],
5960
array $data = [],
60-
\Magento\Framework\Serialize\Serializer\Json $serializer = null
61+
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
62+
\Magento\Framework\Serialize\SerializerInterface $serializerInterface = null
6163
) {
6264
parent::__construct($context, $data);
6365
$this->formKey = $formKey;
6466
$this->_isScopePrivate = true;
6567
$this->jsLayout = isset($data['jsLayout']) && is_array($data['jsLayout']) ? $data['jsLayout'] : [];
6668
$this->configProvider = $configProvider;
6769
$this->layoutProcessors = $layoutProcessors;
68-
$this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
69-
->get(\Magento\Framework\Serialize\Serializer\Json::class);
70+
$this->serializer = $serializerInterface ?: \Magento\Framework\App\ObjectManager::getInstance()
71+
->get(\Magento\Framework\Serialize\Serializer\JsonHexTag::class);
7072
}
7173

7274
/**
73-
* @return string
75+
* @inheritdoc
7476
*/
7577
public function getJsLayout()
7678
{
7779
foreach ($this->layoutProcessors as $processor) {
7880
$this->jsLayout = $processor->process($this->jsLayout);
7981
}
8082

81-
return json_encode($this->jsLayout, JSON_HEX_TAG);
83+
return $this->serializer->serialize($this->jsLayout);
8284
}
8385

8486
/**
@@ -115,11 +117,13 @@ public function getBaseUrl()
115117
}
116118

117119
/**
120+
* Retrieve serialized checkout config.
121+
*
118122
* @return bool|string
119123
* @since 100.2.0
120124
*/
121125
public function getSerializedCheckoutConfig()
122126
{
123-
return json_encode($this->getCheckoutConfig(), JSON_HEX_TAG);
127+
return $this->serializer->serialize($this->getCheckoutConfig());
124128
}
125129
}

app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class OnepageTest extends \PHPUnit\Framework\TestCase
3535
/**
3636
* @var \PHPUnit_Framework_MockObject_MockObject
3737
*/
38-
private $serializer;
38+
private $serializerMock;
3939

4040
protected function setUp()
4141
{
@@ -49,15 +49,16 @@ protected function setUp()
4949
\Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class
5050
);
5151

52-
$this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class);
52+
$this->serializerMock = $this->createMock(\Magento\Framework\Serialize\Serializer\JsonHexTag::class);
5353

5454
$this->model = new \Magento\Checkout\Block\Onepage(
5555
$contextMock,
5656
$this->formKeyMock,
5757
$this->configProviderMock,
5858
[$this->layoutProcessorMock],
5959
[],
60-
$this->serializer
60+
$this->serializerMock,
61+
$this->serializerMock
6162
);
6263
}
6364

@@ -93,6 +94,7 @@ public function testGetJsLayout()
9394
$processedLayout = ['layout' => ['processed' => true]];
9495
$jsonLayout = '{"layout":{"processed":true}}';
9596
$this->layoutProcessorMock->expects($this->once())->method('process')->with([])->willReturn($processedLayout);
97+
$this->serializerMock->expects($this->once())->method('serialize')->willReturn($jsonLayout);
9698

9799
$this->assertEquals($jsonLayout, $this->model->getJsLayout());
98100
}
@@ -101,6 +103,7 @@ public function testGetSerializedCheckoutConfig()
101103
{
102104
$checkoutConfig = ['checkout', 'config'];
103105
$this->configProviderMock->expects($this->once())->method('getConfig')->willReturn($checkoutConfig);
106+
$this->serializerMock->expects($this->once())->method('serialize')->willReturn(json_encode($checkoutConfig));
104107

105108
$this->assertEquals(json_encode($checkoutConfig), $this->model->getSerializedCheckoutConfig());
106109
}

app/code/Magento/Checkout/etc/frontend/di.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<item name="totalsSortOrder" xsi:type="object">Magento\Checkout\Block\Checkout\TotalsProcessor</item>
6060
<item name="directoryData" xsi:type="object">Magento\Checkout\Block\Checkout\DirectoryDataProcessor</item>
6161
</argument>
62+
<argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\JsonHexTag</argument>
6263
</arguments>
6364
</type>
6465
<type name="Magento\Checkout\Block\Cart\Totals">

app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66
namespace Magento\Ui\TemplateEngine\Xhtml;
77

8+
use Magento\Framework\App\ObjectManager;
9+
use Magento\Framework\Serialize\Serializer\JsonHexTag;
810
use Magento\Framework\View\Layout\Generator\Structure;
911
use Magento\Framework\View\Element\UiComponentInterface;
1012
use Magento\Framework\View\TemplateEngine\Xhtml\Template;
@@ -42,25 +44,33 @@ class Result implements ResultInterface
4244
*/
4345
protected $logger;
4446

47+
/**
48+
* @var JsonHexTag
49+
*/
50+
private $jsonSerializer;
51+
4552
/**
4653
* @param Template $template
4754
* @param CompilerInterface $compiler
4855
* @param UiComponentInterface $component
4956
* @param Structure $structure
5057
* @param LoggerInterface $logger
58+
* @param JsonHexTag $jsonSerializer
5159
*/
5260
public function __construct(
5361
Template $template,
5462
CompilerInterface $compiler,
5563
UiComponentInterface $component,
5664
Structure $structure,
57-
LoggerInterface $logger
65+
LoggerInterface $logger,
66+
JsonHexTag $jsonSerializer = null
5867
) {
5968
$this->template = $template;
6069
$this->compiler = $compiler;
6170
$this->component = $component;
6271
$this->structure = $structure;
6372
$this->logger = $logger;
73+
$this->jsonSerializer = $jsonSerializer ?? ObjectManager::getInstance()->get(JsonHexTag::class);
6474
}
6575

6676
/**
@@ -81,7 +91,7 @@ public function getDocumentElement()
8191
public function appendLayoutConfiguration()
8292
{
8393
$layoutConfiguration = $this->wrapContent(
84-
json_encode($this->structure->generate($this->component), JSON_HEX_TAG)
94+
$this->jsonSerializer->serialize($this->structure->generate($this->component))
8595
);
8696
$this->template->append($layoutConfiguration);
8797
}

app/code/Magento/Ui/Test/Unit/TemplateEngine/Xhtml/ResultTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Magento\Ui\Test\Unit\TemplateEngine\Xhtml;
88

9+
use Magento\Framework\Serialize\Serializer\JsonHexTag;
910
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
1011
use Magento\Framework\View\Layout\Generator\Structure;
1112
use Magento\Framework\View\Element\UiComponentInterface;
@@ -58,13 +59,21 @@ class ResultTest extends \PHPUnit\Framework\TestCase
5859
*/
5960
private $objectManager;
6061

62+
/**
63+
* @var JsonHexTag|\PHPUnit_Framework_MockObject_MockObject
64+
*/
65+
private $serializer;
66+
6167
protected function setUp()
6268
{
6369
$this->template = $this->createPartialMock(Template::class, ['append']);
6470
$this->compiler = $this->createMock(CompilerInterface::class);
6571
$this->component = $this->createMock(UiComponentInterface::class);
6672
$this->structure = $this->createPartialMock(Structure::class, ['generate']);
6773
$this->logger = $this->createMock(LoggerInterface::class);
74+
$this->serializer = $this->getMockBuilder(JsonHexTag::class)
75+
->disableOriginalConstructor()
76+
->getMock();
6877

6978
$this->objectManager = new ObjectManager($this);
7079
$this->testModel = $this->objectManager->getObject(Result::class, [
@@ -73,6 +82,7 @@ protected function setUp()
7382
'component' => $this->component,
7483
'structure' => $this->structure,
7584
'logger' => $this->logger,
85+
'jsonSerializer' => $this->serializer
7686
]);
7787
}
7888

@@ -82,6 +92,10 @@ protected function setUp()
8292
public function testAppendLayoutConfiguration()
8393
{
8494
$configWithCdata = 'text before <![CDATA[cdata text]]>';
95+
$this->serializer->expects($this->once())
96+
->method('serialize')
97+
->with([$configWithCdata])
98+
->willReturn('["text before \u003C![CDATA[cdata text]]\u003E"]');
8599
$this->structure->expects($this->once())
86100
->method('generate')
87101
->with($this->component)

lib/internal/Magento/Framework/Serialize/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
**Serialize** library provides interface *SerializerInterface* and multiple implementations:
44

55
* *Json* - default implementation. Uses PHP native json_encode/json_decode functions;
6+
* *JsonHexTag* - default implementation. Uses PHP native json_encode/json_decode functions with `JSON_HEX_TAG` option enabled;
67
* *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.
78

89
Using *Serialize* implementation directly is discouraged, always use *SerializerInterface*, using *Serialize* implementation may lead to security vulnerabilities.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Serialize\Serializer;
10+
11+
use Magento\Framework\Serialize\SerializerInterface;
12+
13+
/**
14+
* Serialize data to JSON with the JSON_HEX_TAG option enabled
15+
* (All < and > are converted to \u003C and \u003E),
16+
* unserialize JSON encoded data
17+
*
18+
* @api
19+
* @since 100.2.0
20+
*/
21+
class JsonHexTag extends Json implements SerializerInterface
22+
{
23+
/**
24+
* @inheritDoc
25+
* @since 100.2.0
26+
*/
27+
public function serialize($data): string
28+
{
29+
$result = json_encode($data, JSON_HEX_TAG);
30+
if (false === $result) {
31+
throw new \InvalidArgumentException('Unable to serialize value.');
32+
}
33+
return $result;
34+
}
35+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Serialize\Test\Unit\Serializer;
10+
11+
use Magento\Framework\DataObject;
12+
use Magento\Framework\Serialize\Serializer\JsonHexTag;
13+
14+
class JsonHexTagTest extends \PHPUnit\Framework\TestCase
15+
{
16+
/**
17+
* @var \Magento\Framework\Serialize\Serializer\Json
18+
*/
19+
private $json;
20+
21+
protected function setUp()
22+
{
23+
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
24+
$this->json = $objectManager->getObject(JsonHexTag::class);
25+
}
26+
27+
/**
28+
* @param string|int|float|bool|array|null $value
29+
* @param string $expected
30+
* @dataProvider serializeDataProvider
31+
*/
32+
public function testSerialize($value, $expected)
33+
{
34+
$this->assertEquals(
35+
$expected,
36+
$this->json->serialize($value)
37+
);
38+
}
39+
40+
public function serializeDataProvider()
41+
{
42+
$dataObject = new DataObject(['something']);
43+
return [
44+
['', '""'],
45+
['string', '"string"'],
46+
[null, 'null'],
47+
[false, 'false'],
48+
[['a' => 'b', 'd' => 123], '{"a":"b","d":123}'],
49+
[123, '123'],
50+
[10.56, '10.56'],
51+
[$dataObject, '{}'],
52+
['< >', '"\u003C \u003E"'],
53+
];
54+
}
55+
56+
/**
57+
* @param string $value
58+
* @param string|int|float|bool|array|null $expected
59+
* @dataProvider unserializeDataProvider
60+
*/
61+
public function testUnserialize($value, $expected)
62+
{
63+
$this->assertEquals(
64+
$expected,
65+
$this->json->unserialize($value)
66+
);
67+
}
68+
69+
/**
70+
* @return array
71+
*/
72+
public function unserializeDataProvider(): array
73+
{
74+
return [
75+
['""', ''],
76+
['"string"', 'string'],
77+
['null', null],
78+
['false', false],
79+
['{"a":"b","d":123}', ['a' => 'b', 'd' => 123]],
80+
['123', 123],
81+
['10.56', 10.56],
82+
['{}', []],
83+
['"\u003C \u003E"', '< >'],
84+
];
85+
}
86+
87+
/**
88+
* @expectedException \InvalidArgumentException
89+
* @expectedExceptionMessage Unable to serialize value.
90+
*/
91+
public function testSerializeException()
92+
{
93+
$this->json->serialize(STDOUT);
94+
}
95+
96+
/**
97+
* @expectedException \InvalidArgumentException
98+
* @expectedExceptionMessage Unable to unserialize value.
99+
* @dataProvider unserializeExceptionDataProvider
100+
*/
101+
public function testUnserializeException($value)
102+
{
103+
$this->json->unserialize($value);
104+
}
105+
106+
/**
107+
* @return array
108+
*/
109+
public function unserializeExceptionDataProvider(): array
110+
{
111+
return [
112+
[''],
113+
[false],
114+
[null],
115+
['{']
116+
];
117+
}
118+
}

0 commit comments

Comments
 (0)