Skip to content

Commit 1284602

Browse files
author
Igor Melnikov
committed
Merge branch 'pr' of github.com:magento-jackalopes/magento2ce into pr
2 parents 0767ced + 9938530 commit 1284602

File tree

2 files changed

+115
-14
lines changed

2 files changed

+115
-14
lines changed

lib/internal/Magento/Framework/Escaper.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,12 @@ public function escapeJs($string)
234234
return preg_replace_callback(
235235
'/[^a-z0-9,\._]/iSu',
236236
function ($matches) {
237-
return sprintf('\\u%04s', strtoupper(bin2hex($matches[0])));
237+
$chr = $matches[0];
238+
if (strlen($chr) != 1) {
239+
$chr = mb_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
240+
$chr = ($chr === false) ? '' : $chr;
241+
}
242+
return sprintf('\\u%04s', strtoupper(bin2hex($chr)));
238243
},
239244
$string
240245
);

lib/internal/Magento/Framework/Test/Unit/EscaperTest.php

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class EscaperTest extends \PHPUnit_Framework_TestCase
1616
/**
1717
* @var \Magento\Framework\Escaper
1818
*/
19-
protected $_escaper = null;
19+
protected $escaper = null;
2020

2121
/**
2222
* @var \Magento\Framework\ZendEscaper
@@ -30,12 +30,108 @@ class EscaperTest extends \PHPUnit_Framework_TestCase
3030

3131
protected function setUp()
3232
{
33-
$this->_escaper = new Escaper();
33+
$this->escaper = new Escaper();
3434
$this->zendEscaper = new \Magento\Framework\ZendEscaper();
3535
$this->loggerMock = $this->getMockForAbstractClass(\Psr\Log\LoggerInterface::class);
3636
$objectManagerHelper = new ObjectManager($this);
37-
$objectManagerHelper->setBackwardCompatibleProperty($this->_escaper, 'escaper', $this->zendEscaper);
38-
$objectManagerHelper->setBackwardCompatibleProperty($this->_escaper, 'logger', $this->loggerMock);
37+
$objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'escaper', $this->zendEscaper);
38+
$objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'logger', $this->loggerMock);
39+
}
40+
41+
/**
42+
* Convert a Unicode Codepoint to a literal UTF-8 character.
43+
*
44+
* @param int $codepoint Unicode codepoint in hex notation
45+
* @return string UTF-8 literal string
46+
*/
47+
protected function codepointToUtf8($codepoint)
48+
{
49+
if ($codepoint < 0x80) {
50+
return chr($codepoint);
51+
}
52+
if ($codepoint < 0x800) {
53+
return chr($codepoint >> 6 & 0x3f | 0xc0)
54+
. chr($codepoint & 0x3f | 0x80);
55+
}
56+
if ($codepoint < 0x10000) {
57+
return chr($codepoint >> 12 & 0x0f | 0xe0)
58+
. chr($codepoint >> 6 & 0x3f | 0x80)
59+
. chr($codepoint & 0x3f | 0x80);
60+
}
61+
if ($codepoint < 0x110000) {
62+
return chr($codepoint >> 18 & 0x07 | 0xf0)
63+
. chr($codepoint >> 12 & 0x3f | 0x80)
64+
. chr($codepoint >> 6 & 0x3f | 0x80)
65+
. chr($codepoint & 0x3f | 0x80);
66+
}
67+
throw new \Exception('Codepoint requested outside of Unicode range');
68+
}
69+
70+
public function testEscapeJsEscapesOwaspRecommendedRanges()
71+
{
72+
$immune = [',', '.', '_']; // Exceptions to escaping ranges
73+
for ($chr=0; $chr < 0xFF; $chr++) {
74+
if ($chr >= 0x30 && $chr <= 0x39
75+
|| $chr >= 0x41 && $chr <= 0x5A
76+
|| $chr >= 0x61 && $chr <= 0x7A
77+
) {
78+
$literal = $this->codepointToUtf8($chr);
79+
$this->assertEquals($literal, $this->escaper->escapeJs($literal));
80+
} else {
81+
$literal = $this->codepointToUtf8($chr);
82+
if (in_array($literal, $immune)) {
83+
$this->assertEquals($literal, $this->escaper->escapeJs($literal));
84+
} else {
85+
$this->assertNotEquals(
86+
$literal,
87+
$this->escaper->escapeJs($literal),
88+
$literal . ' should be escaped!'
89+
);
90+
}
91+
}
92+
}
93+
}
94+
95+
/**
96+
* @param string $data
97+
* @param string $expected
98+
* @dataProvider escapeJsDataProvider
99+
*/
100+
public function testEscapeJs($data, $expected)
101+
{
102+
$this->assertEquals($expected, $this->escaper->escapeJs($data));
103+
}
104+
105+
public function escapeJsDataProvider()
106+
{
107+
return [
108+
'zero length string' => ['', ''],
109+
'only digits' => ['123', '123'],
110+
'<' => ['<', '\u003C'],
111+
'>' => ['>', '\\u003E'],
112+
'\'' => ['\'', '\\u0027'],
113+
'"' => ['"', '\\u0022'],
114+
'&' => ['&', '\\u0026'],
115+
'Characters beyond ASCII value 255 to unicode escape' => ['Ā', '\\u0100'],
116+
'Characters beyond Unicode BMP to unicode escape' => ["\xF0\x90\x80\x80", '\\uD800DC00'],
117+
/* Immune chars excluded */
118+
',' => [',', ','],
119+
'.' => ['.', '.'],
120+
'_' => ['_', '_'],
121+
/* Basic alnums exluded */
122+
'a' => ['a', 'a'],
123+
'A' => ['A', 'A'],
124+
'z' => ['z', 'z'],
125+
'Z' => ['Z', 'Z'],
126+
'0' => ['0', '0'],
127+
'9' => ['9', '9'],
128+
/* Basic control characters and null */
129+
"\r" => ["\r", '\\u000D'],
130+
"\n" => ["\n", '\\u000A'],
131+
"\t" => ["\t", '\\u0009'],
132+
"\0" => ["\0", '\\u0000'],
133+
'Encode spaces for quoteless attribute protection' => [' ', '\\u0020'],
134+
];
39135
}
40136

41137
/**
@@ -44,7 +140,7 @@ protected function setUp()
44140
*/
45141
public function testEscapeHtml($data, $expected, $allowedTags = [])
46142
{
47-
$actual = $this->_escaper->escapeHtml($data, $allowedTags);
143+
$actual = $this->escaper->escapeHtml($data, $allowedTags);
48144
$this->assertEquals($expected, $actual);
49145
}
50146

@@ -56,7 +152,7 @@ public function testEscapeHtmlWithInvalidData($data, $expected, $allowedTags = [
56152
{
57153
$this->loggerMock->expects($this->once())
58154
->method('critical');
59-
$actual = $this->_escaper->escapeHtml($data, $allowedTags);
155+
$actual = $this->escaper->escapeHtml($data, $allowedTags);
60156
$this->assertEquals($expected, $actual);
61157
}
62158

@@ -160,8 +256,8 @@ public function testEscapeUrl()
160256
{
161257
$data = 'http://example.com/search?term=this+%26+that&view=list';
162258
$expected = 'http://example.com/search?term=this+%26+that&amp;view=list';
163-
$this->assertEquals($expected, $this->_escaper->escapeUrl($data));
164-
$this->assertEquals($expected, $this->_escaper->escapeUrl($expected));
259+
$this->assertEquals($expected, $this->escaper->escapeUrl($data));
260+
$this->assertEquals($expected, $this->escaper->escapeUrl($expected));
165261
}
166262

167263
/**
@@ -171,8 +267,8 @@ public function testEscapeJsQuote()
171267
{
172268
$data = ["Don't do that.", 'lost_key' => "Can't do that."];
173269
$expected = ["Don\\'t do that.", "Can\\'t do that."];
174-
$this->assertEquals($expected, $this->_escaper->escapeJsQuote($data));
175-
$this->assertEquals($expected[0], $this->_escaper->escapeJsQuote($data[0]));
270+
$this->assertEquals($expected, $this->escaper->escapeJsQuote($data));
271+
$this->assertEquals($expected[0], $this->escaper->escapeJsQuote($data[0]));
176272
}
177273

178274
/**
@@ -185,8 +281,8 @@ public function testEscapeQuote()
185281
"Text with &#039;single&#039; and &quot;double&quot; quotes",
186282
"Text with \\&#039;single\\&#039; and \\&quot;double\\&quot; quotes",
187283
];
188-
$this->assertEquals($expected[0], $this->_escaper->escapeQuote($data));
189-
$this->assertEquals($expected[1], $this->_escaper->escapeQuote($data, true));
284+
$this->assertEquals($expected[0], $this->escaper->escapeQuote($data));
285+
$this->assertEquals($expected[1], $this->escaper->escapeQuote($data, true));
190286
}
191287

192288
/**
@@ -197,7 +293,7 @@ public function testEscapeQuote()
197293
*/
198294
public function testEscapeXssInUrl($input, $expected)
199295
{
200-
$this->assertEquals($expected, $this->_escaper->escapeXssInUrl($input));
296+
$this->assertEquals($expected, $this->escaper->escapeXssInUrl($input));
201297
}
202298

203299
/**

0 commit comments

Comments
 (0)