Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
EscaperTest.php
Go to the documentation of this file.
1 <?php
7 
10 
14 class EscaperTest extends \PHPUnit\Framework\TestCase
15 {
19  protected $escaper = null;
20 
24  private $zendEscaper;
25 
29  private $loggerMock;
30 
31  protected function setUp()
32  {
33  $this->escaper = new Escaper();
34  $this->zendEscaper = new \Magento\Framework\ZendEscaper();
35  $this->loggerMock = $this->getMockForAbstractClass(\Psr\Log\LoggerInterface::class);
36  $objectManagerHelper = new ObjectManager($this);
37  $objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'escaper', $this->zendEscaper);
38  $objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'logger', $this->loggerMock);
39  }
40 
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 
71  {
72  // Exceptions to escaping ranges
73  $immune = [',', '.', '_'];
74  for ($chr = 0; $chr < 0xFF; $chr++) {
75  if ($chr >= 0x30 && $chr <= 0x39
76  || $chr >= 0x41 && $chr <= 0x5A
77  || $chr >= 0x61 && $chr <= 0x7A
78  ) {
79  $literal = $this->codepointToUtf8($chr);
80  $this->assertEquals($literal, $this->escaper->escapeJs($literal));
81  } else {
82  $literal = $this->codepointToUtf8($chr);
83  if (in_array($literal, $immune)) {
84  $this->assertEquals($literal, $this->escaper->escapeJs($literal));
85  } else {
86  $this->assertNotEquals(
87  $literal,
88  $this->escaper->escapeJs($literal),
89  $literal . ' should be escaped!'
90  );
91  }
92  }
93  }
94  }
95 
101  public function testEscapeJs($data, $expected)
102  {
103  $this->assertEquals($expected, $this->escaper->escapeJs($data));
104  }
105 
109  public function escapeJsDataProvider()
110  {
111  return [
112  'zero length string' => ['', ''],
113  'only digits' => ['123', '123'],
114  '<' => ['<', '\u003C'],
115  '>' => ['>', '\\u003E'],
116  '\'' => ['\'', '\\u0027'],
117  '"' => ['"', '\\u0022'],
118  '&' => ['&', '\\u0026'],
119  'Characters beyond ASCII value 255 to unicode escape' => ['Ā', '\\u0100'],
120  'Characters beyond Unicode BMP to unicode escape' => ["\xF0\x90\x80\x80", '\\uD800DC00'],
121  /* Immune chars excluded */
122  ',' => [',', ','],
123  '.' => ['.', '.'],
124  '_' => ['_', '_'],
125  /* Basic alnums exluded */
126  'a' => ['a', 'a'],
127  'A' => ['A', 'A'],
128  'z' => ['z', 'z'],
129  'Z' => ['Z', 'Z'],
130  '0' => ['0', '0'],
131  '9' => ['9', '9'],
132  /* Basic control characters and null */
133  "\r" => ["\r", '\\u000D'],
134  "\n" => ["\n", '\\u000A'],
135  "\t" => ["\t", '\\u0009'],
136  "\0" => ["\0", '\\u0000'],
137  'Encode spaces for quoteless attribute protection' => [' ', '\\u0020'],
138  ];
139  }
140 
145  public function testEscapeHtml($data, $expected, $allowedTags = [])
146  {
147  $actual = $this->escaper->escapeHtml($data, $allowedTags);
148  $this->assertEquals($expected, $actual);
149  }
150 
155  public function testEscapeHtmlWithInvalidData($data, $expected, $allowedTags = [])
156  {
157  $this->loggerMock->expects($this->once())
158  ->method('critical');
159  $actual = $this->escaper->escapeHtml($data, $allowedTags);
160  $this->assertEquals($expected, $actual);
161  }
162 
166  public function escapeHtmlDataProvider()
167  {
168  return [
169  'array -> [text with no tags, text with no allowed tags]' => [
170  'data' => ['one', '<two>three</two>'],
171  'expected' => ['one', '&lt;two&gt;three&lt;/two&gt;'],
172  ],
173  'text with special characters' => [
174  'data' => '&<>"\'&amp;&lt;&gt;&quot;&#039;&#9;',
175  'expected' => '&amp;&lt;&gt;&quot;&#039;&amp;&lt;&gt;&quot;&#039;&#9;'
176  ],
177  'text with multiple allowed tags, includes self closing tag' => [
178  'data' => '<span>some text in tags<br /></span>',
179  'expected' => '<span>some text in tags<br></span>',
180  'allowedTags' => ['span', 'br'],
181  ],
182  'text with multiple allowed tags and allowed attribute in double quotes' => [
183  'data' => 'Only <span id="sku_max_allowed"><b>2</b></span> in stock',
184  'expected' => 'Only <span id="sku_max_allowed"><b>2</b></span> in stock',
185  'allowedTags' => ['span', 'b'],
186  ],
187  'text with multiple allowed tags and allowed attribute in single quotes' => [
188  'data' => 'Only <span id=\'sku_max_allowed\'><b>2</b></span> in stock',
189  'expected' => 'Only <span id="sku_max_allowed"><b>2</b></span> in stock',
190  'allowedTags' => ['span', 'b'],
191  ],
192  'text with multiple allowed tags with allowed attribute' => [
193  'data' => 'Only registered users can write reviews. Please <a href="%1">Sign in</a> or <a href="%2">'
194  . 'create an account</a>',
195  'expected' => 'Only registered users can write reviews. Please <a href="%1">Sign in</a> or '
196  . '<a href="%2">create an account</a>',
197  'allowedTags' => ['a'],
198  ],
199  'text with not allowed attribute in single quotes' => [
200  'data' => 'Only <span type=\'1\'><b>2</b></span> in stock',
201  'expected' => 'Only <span><b>2</b></span> in stock',
202  'allowedTags' => ['span', 'b'],
203  ],
204  'text with allowed and not allowed tags' => [
205  'data' => 'Only registered users can write reviews. Please <a href="%1">Sign in<span>three</span></a> '
206  . 'or <a href="%2"><span id="action">create an account</span></a>',
207  'expected' => 'Only registered users can write reviews. Please <a href="%1">Sign inthree</a> or '
208  . '<a href="%2">create an account</a>',
209  'allowedTags' => ['a'],
210  ],
211  'text with allowed and not allowed tags, with allowed and not allowed attributes' => [
212  'data' => 'Some test <span>text in span tag</span> <strong>text in strong tag</strong> '
213  . '<a type="some-type" href="http://domain.com/" onclick="alert(1)">Click here</a><script>alert(1)'
214  . '</script>',
215  'expected' => 'Some test <span>text in span tag</span> text in strong tag <a href="http://domain.com/">'
216  . 'Click here</a>alert(1)',
217  'allowedTags' => ['a', 'span'],
218  ],
219  'text with html comment' => [
220  'data' => 'Only <span><b>2</b></span> in stock <!-- HTML COMMENT -->',
221  'expected' => 'Only <span><b>2</b></span> in stock <!-- HTML COMMENT -->',
222  'allowedTags' => ['span', 'b'],
223  ],
224  'text with non ascii characters' => [
225  'data' => ['абвгд', 'مثال', '幸福'],
226  'expected' => ['абвгд', 'مثال', '幸福'],
227  'allowedTags' => [],
228  ],
229  'html and body tags' => [
230  'data' => '<html><body><span>String</span></body></html>',
231  'expected' => '<span>String</span>',
232  'allowedTags' => ['span'],
233  ],
234  'invalid tag' => [
235  'data' => '<some tag> some text',
236  'expected' => ' some text',
237  'allowedTags' => ['span'],
238  ],
239  ];
240  }
241 
246  {
247  return [
248  'text with allowed script tag' => [
249  'data' => '<span><script>some text in tags</script></span>',
250  'expected' => '<span>some text in tags</span>',
251  'allowedTags' => ['span', 'script'],
252  ],
253  'text with invalid html' => [
254  'data' => '<spa>n id="id1">Some string</span>',
255  'expected' => 'n id=&quot;id1&quot;&gt;Some string',
256  'allowedTags' => ['span'],
257  ],
258  ];
259  }
260 
264  public function testEscapeUrl()
265  {
266  $data = 'http://example.com/search?term=this+%26+that&view=list';
267  $expected = 'http://example.com/search?term=this+%26+that&amp;view=list';
268  $this->assertEquals($expected, $this->escaper->escapeUrl($data));
269  $this->assertEquals($expected, $this->escaper->escapeUrl($expected));
270  }
271 
275  public function testEscapeJsQuote()
276  {
277  $data = ["Don't do that.", 'lost_key' => "Can't do that."];
278  $expected = ["Don\\'t do that.", "Can\\'t do that."];
279  $this->assertEquals($expected, $this->escaper->escapeJsQuote($data));
280  $this->assertEquals($expected[0], $this->escaper->escapeJsQuote($data[0]));
281  }
282 
286  public function testEscapeQuote()
287  {
288  $data = "Text with 'single' and \"double\" quotes";
289  $expected = [
290  "Text with &#039;single&#039; and &quot;double&quot; quotes",
291  "Text with \\&#039;single\\&#039; and \\&quot;double\\&quot; quotes",
292  ];
293  $this->assertEquals($expected[0], $this->escaper->escapeQuote($data));
294  $this->assertEquals($expected[1], $this->escaper->escapeQuote($data, true));
295  }
296 
303  public function testEscapeXssInUrl($input, $expected)
304  {
305  $this->assertEquals($expected, $this->escaper->escapeXssInUrl($input));
306  }
307 
312  public function escapeDataProvider()
313  {
314  return [
315  [
316  'javascript%3Aalert%28String.fromCharCode%280x78%29%2BString.'
317  . 'fromCharCode%280x73%29%2BString.fromCharCode%280x73%29%29',
318  ':alert%28String.fromCharCode%280x78%29%2BString.'
319  . 'fromCharCode%280x73%29%2BString.fromCharCode%280x73%29%29'
320  ],
321  [
322  'http://test.com/?redirect=JAVASCRIPT:alert%281%29',
323  'http://test.com/?redirect=:alert%281%29',
324  ],
325  [
326  'http://test.com/?redirect=javascript:alert%281%29',
327  'http://test.com/?redirect=:alert%281%29',
328  ],
329  [
330  'http://test.com/?redirect=JavaScript:alert%281%29',
331  'http://test.com/?redirect=:alert%281%29',
332  ],
333  [
334  'http://test.com/?redirect=javascript:alert(1)',
335  'http://test.com/?redirect=:alert(1)',
336  ],
337  [
338  'http://test.com/?redirect=javascript:alert(1)&test=1',
339  'http://test.com/?redirect=:alert(1)&amp;test=1',
340  ],
341  [
342  'http://test.com/?redirect=\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74:alert(1)',
343  'http://test.com/?redirect=:alert(1)',
344  ],
345  [
346  'http://test.com/?redirect=vbscript:alert(1)',
347  'http://test.com/?redirect=:alert(1)',
348  ],
349  [
350  'http://test.com/?redirect=data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg',
351  'http://test.com/?redirect=:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg',
352  ],
353  [
354  'http://test.com/?redirect=data%3Atext%2Fhtml%3Bbase64%2CPHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg',
355  'http://test.com/?redirect=:text%2Fhtml%3Bbase64%2CPHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg',
356  ],
357  [
358  'http://test.com/?redirect=\x64\x61\x74\x61\x3a\x74\x65\x78\x74x2cCPHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg',
359  'http://test.com/?redirect=:\x74\x65\x78\x74x2cCPHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg',
360  ],
361  ];
362  }
363 }
testEscapeHtml($data, $expected, $allowedTags=[])
testEscapeHtmlWithInvalidData($data, $expected, $allowedTags=[])