|
3 | 3 | * Copyright © Magento, Inc. All rights reserved.
|
4 | 4 | * See COPYING.txt for license details.
|
5 | 5 | */
|
| 6 | + |
6 | 7 | namespace Magento\Framework;
|
7 | 8 |
|
8 | 9 | /**
|
@@ -32,38 +33,43 @@ class Escaper
|
32 | 33 | */
|
33 | 34 | private $allowedAttributes = ['id', 'class', 'href', 'target', 'title', 'style'];
|
34 | 35 |
|
| 36 | + /** |
| 37 | + * @var string |
| 38 | + */ |
| 39 | + private static $xssFiltrationPattern = |
| 40 | + '/((javascript(\\\\x3a|:|%3A))|(data(\\\\x3a|:|%3A))|(vbscript:))|' |
| 41 | + . '((\\\\x6A\\\\x61\\\\x76\\\\x61\\\\x73\\\\x63\\\\x72\\\\x69\\\\x70\\\\x74(\\\\x3a|:|%3A))|' |
| 42 | + . '(\\\\x64\\\\x61\\\\x74\\\\x61(\\\\x3a|:|%3A)))/i'; |
| 43 | + |
35 | 44 | /**
|
36 | 45 | * @var string[]
|
37 | 46 | */
|
38 | 47 | private $escapeAsUrlAttributes = ['href'];
|
39 | 48 |
|
40 | 49 | /**
|
41 |
| - * Escape string for HTML context. allowedTags will not be escaped, except the following: script, img, embed, |
42 |
| - * iframe, video, source, object, audio |
| 50 | + * Escape string for HTML context. |
| 51 | + * |
| 52 | + * AllowedTags will not be escaped, except the following: script, img, embed, |
| 53 | + * iframe, video, source, object, audio. |
43 | 54 | *
|
44 | 55 | * @param string|array $data
|
45 | 56 | * @param array|null $allowedTags
|
46 | 57 | * @return string|array
|
47 | 58 | */
|
48 | 59 | public function escapeHtml($data, $allowedTags = null)
|
49 | 60 | {
|
| 61 | + if (!is_array($data)) { |
| 62 | + $data = (string)$data; |
| 63 | + } |
| 64 | + |
50 | 65 | if (is_array($data)) {
|
51 | 66 | $result = [];
|
52 | 67 | foreach ($data as $item) {
|
53 | 68 | $result[] = $this->escapeHtml($item, $allowedTags);
|
54 | 69 | }
|
55 | 70 | } elseif (strlen($data)) {
|
56 | 71 | if (is_array($allowedTags) && !empty($allowedTags)) {
|
57 |
| - $notAllowedTags = array_intersect( |
58 |
| - array_map('strtolower', $allowedTags), |
59 |
| - $this->notAllowedTags |
60 |
| - ); |
61 |
| - if (!empty($notAllowedTags)) { |
62 |
| - $this->getLogger()->critical( |
63 |
| - 'The following tag(s) are not allowed: ' . implode(', ', $notAllowedTags) |
64 |
| - ); |
65 |
| - $allowedTags = array_diff($allowedTags, $this->notAllowedTags); |
66 |
| - } |
| 72 | + $allowedTags = $this->filterProhibitedTags($allowedTags); |
67 | 73 | $wrapperElementId = uniqid();
|
68 | 74 | $domDocument = new \DOMDocument('1.0', 'UTF-8');
|
69 | 75 | set_error_handler(
|
@@ -197,7 +203,7 @@ public function escapeHtmlAttr($string, $escapeSingleQuote = true)
|
197 | 203 | if ($escapeSingleQuote) {
|
198 | 204 | return $this->getEscaper()->escapeHtmlAttr((string) $string);
|
199 | 205 | }
|
200 |
| - return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false); |
| 206 | + return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8', false); |
201 | 207 | }
|
202 | 208 |
|
203 | 209 | /**
|
@@ -278,30 +284,47 @@ public function escapeJsQuote($data, $quote = '\'')
|
278 | 284 | $result[] = $this->escapeJsQuote($item, $quote);
|
279 | 285 | }
|
280 | 286 | } else {
|
281 |
| - $result = str_replace($quote, '\\' . $quote, $data); |
| 287 | + $result = str_replace($quote, '\\' . $quote, (string)$data); |
282 | 288 | }
|
283 | 289 | return $result;
|
284 | 290 | }
|
285 | 291 |
|
286 | 292 | /**
|
287 | 293 | * Escape xss in urls
|
288 |
| - * Remove `javascript:`, `vbscript:`, `data:` words from url |
289 | 294 | *
|
290 | 295 | * @param string $data
|
291 | 296 | * @return string
|
292 | 297 | * @deprecated 100.2.0
|
293 | 298 | */
|
294 | 299 | public function escapeXssInUrl($data)
|
295 | 300 | {
|
296 |
| - $pattern = '/((javascript(\\\\x3a|:|%3A))|(data(\\\\x3a|:|%3A))|(vbscript:))|' |
297 |
| - . '((\\\\x6A\\\\x61\\\\x76\\\\x61\\\\x73\\\\x63\\\\x72\\\\x69\\\\x70\\\\x74(\\\\x3a|:|%3A))|' |
298 |
| - . '(\\\\x64\\\\x61\\\\x74\\\\x61(\\\\x3a|:|%3A)))/i'; |
299 |
| - $result = preg_replace($pattern, ':', $data); |
300 |
| - return htmlspecialchars($result, ENT_COMPAT | ENT_HTML5 | ENT_HTML401, 'UTF-8', false); |
| 301 | + return htmlspecialchars( |
| 302 | + $this->escapeScriptIdentifiers((string)$data), |
| 303 | + ENT_COMPAT | ENT_HTML5 | ENT_HTML401, |
| 304 | + 'UTF-8', |
| 305 | + false |
| 306 | + ); |
| 307 | + } |
| 308 | + |
| 309 | + /** |
| 310 | + * Remove `javascript:`, `vbscript:`, `data:` words from the string. |
| 311 | + * |
| 312 | + * @param string $data |
| 313 | + * @return string |
| 314 | + */ |
| 315 | + private function escapeScriptIdentifiers(string $data): string |
| 316 | + { |
| 317 | + $filteredData = preg_replace(self::$xssFiltrationPattern, ':', $data) ?: ''; |
| 318 | + if (preg_match(self::$xssFiltrationPattern, $filteredData)) { |
| 319 | + $filteredData = $this->escapeScriptIdentifiers($filteredData); |
| 320 | + } |
| 321 | + |
| 322 | + return $filteredData; |
301 | 323 | }
|
302 | 324 |
|
303 | 325 | /**
|
304 | 326 | * Escape quotes inside html attributes
|
| 327 | + * |
305 | 328 | * Use $addSlashes = false for escaping js that inside html attribute (onClick, onSubmit etc)
|
306 | 329 | *
|
307 | 330 | * @param string $data
|
@@ -346,4 +369,27 @@ private function getLogger()
|
346 | 369 | }
|
347 | 370 | return $this->logger;
|
348 | 371 | }
|
| 372 | + |
| 373 | + /** |
| 374 | + * Filter prohibited tags. |
| 375 | + * |
| 376 | + * @param string[] $allowedTags |
| 377 | + * @return string[] |
| 378 | + */ |
| 379 | + private function filterProhibitedTags(array $allowedTags): array |
| 380 | + { |
| 381 | + $notAllowedTags = array_intersect( |
| 382 | + array_map('strtolower', $allowedTags), |
| 383 | + $this->notAllowedTags |
| 384 | + ); |
| 385 | + |
| 386 | + if (!empty($notAllowedTags)) { |
| 387 | + $this->getLogger()->critical( |
| 388 | + 'The following tag(s) are not allowed: ' . implode(', ', $notAllowedTags) |
| 389 | + ); |
| 390 | + $allowedTags = array_diff($allowedTags, $this->notAllowedTags); |
| 391 | + } |
| 392 | + |
| 393 | + return $allowedTags; |
| 394 | + } |
349 | 395 | }
|
0 commit comments