Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Escaper.php
Go to the documentation of this file.
1 <?php
7 namespace Magento\Framework;
8 
15 class Escaper
16 {
20  private $escaper;
21 
25  private $logger;
26 
30  private $notAllowedTags = ['script', 'img', 'embed', 'iframe', 'video', 'source', 'object', 'audio'];
31 
35  private $allowedAttributes = ['id', 'class', 'href', 'target', 'title', 'style'];
36 
40  private static $xssFiltrationPattern =
41  '/((javascript(\\\\x3a|:|%3A))|(data(\\\\x3a|:|%3A))|(vbscript:))|'
42  . '((\\\\x6A\\\\x61\\\\x76\\\\x61\\\\x73\\\\x63\\\\x72\\\\x69\\\\x70\\\\x74(\\\\x3a|:|%3A))|'
43  . '(\\\\x64\\\\x61\\\\x74\\\\x61(\\\\x3a|:|%3A)))/i';
44 
48  private $escapeAsUrlAttributes = ['href'];
49 
60  public function escapeHtml($data, $allowedTags = null)
61  {
62  if (!is_array($data)) {
63  $data = (string)$data;
64  }
65 
66  if (is_array($data)) {
67  $result = [];
68  foreach ($data as $item) {
69  $result[] = $this->escapeHtml($item, $allowedTags);
70  }
71  } elseif (strlen($data)) {
72  if (is_array($allowedTags) && !empty($allowedTags)) {
73  $allowedTags = $this->filterProhibitedTags($allowedTags);
74  $wrapperElementId = uniqid();
75  $domDocument = new \DOMDocument('1.0', 'UTF-8');
76  set_error_handler(
77  function ($errorNumber, $errorString) {
78  throw new \Exception($errorString, $errorNumber);
79  }
80  );
81  $string = mb_convert_encoding($data, 'HTML-ENTITIES', 'UTF-8');
82  try {
83  $domDocument->loadHTML(
84  '<html><body id="' . $wrapperElementId . '">' . $string . '</body></html>'
85  );
86  } catch (\Exception $e) {
87  restore_error_handler();
88  $this->getLogger()->critical($e);
89  }
90  restore_error_handler();
91 
92  $this->removeNotAllowedTags($domDocument, $allowedTags);
93  $this->removeNotAllowedAttributes($domDocument);
94  $this->escapeText($domDocument);
95  $this->escapeAttributeValues($domDocument);
96 
97  $result = mb_convert_encoding($domDocument->saveHTML(), 'UTF-8', 'HTML-ENTITIES');
98  preg_match('/<body id="' . $wrapperElementId . '">(.+)<\/body><\/html>$/si', $result, $matches);
99  return !empty($matches) ? $matches[1] : '';
100  } else {
101  $result = htmlspecialchars($data, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', false);
102  }
103  } else {
104  $result = $data;
105  }
106  return $result;
107  }
108 
116  private function removeNotAllowedTags(\DOMDocument $domDocument, array $allowedTags)
117  {
118  $xpath = new \DOMXPath($domDocument);
119  $nodes = $xpath->query(
120  '//node()[name() != \''
121  . implode('\' and name() != \'', array_merge($allowedTags, ['html', 'body']))
122  . '\']'
123  );
124  foreach ($nodes as $node) {
125  if ($node->nodeName != '#text' && $node->nodeName != '#comment') {
126  $node->parentNode->replaceChild($domDocument->createTextNode($node->textContent), $node);
127  }
128  }
129  }
130 
137  private function removeNotAllowedAttributes(\DOMDocument $domDocument)
138  {
139  $xpath = new \DOMXPath($domDocument);
140  $nodes = $xpath->query(
141  '//@*[name() != \'' . implode('\' and name() != \'', $this->allowedAttributes) . '\']'
142  );
143  foreach ($nodes as $node) {
144  $node->parentNode->removeAttribute($node->nodeName);
145  }
146  }
147 
154  private function escapeText(\DOMDocument $domDocument)
155  {
156  $xpath = new \DOMXPath($domDocument);
157  $nodes = $xpath->query('//text()');
158  foreach ($nodes as $node) {
159  $node->textContent = $this->escapeHtml($node->textContent);
160  }
161  }
162 
169  private function escapeAttributeValues(\DOMDocument $domDocument)
170  {
171  $xpath = new \DOMXPath($domDocument);
172  $nodes = $xpath->query('//@*');
173  foreach ($nodes as $node) {
174  $value = $this->escapeAttributeValue(
175  $node->nodeName,
176  $node->parentNode->getAttribute($node->nodeName)
177  );
178  $node->parentNode->setAttribute($node->nodeName, $value);
179  }
180  }
181 
189  private function escapeAttributeValue($name, $value)
190  {
191  return in_array($name, $this->escapeAsUrlAttributes) ? $this->escapeUrl($value) : $this->escapeHtml($value);
192  }
193 
202  public function escapeHtmlAttr($string, $escapeSingleQuote = true)
203  {
204  if ($escapeSingleQuote) {
205  return $this->getEscaper()->escapeHtmlAttr((string) $string);
206  }
207  return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8', false);
208  }
209 
216  public function escapeUrl($string)
217  {
218  return $this->escapeHtml($this->escapeXssInUrl($string));
219  }
220 
228  public function encodeUrlParam($string)
229  {
230  return $this->getEscaper()->escapeUrl($string);
231  }
232 
240  public function escapeJs($string)
241  {
242  if ($string === '' || ctype_digit($string)) {
243  return $string;
244  }
245 
246  return preg_replace_callback(
247  '/[^a-z0-9,\._]/iSu',
248  function ($matches) {
249  $chr = $matches[0];
250  if (strlen($chr) != 1) {
251  $chr = mb_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
252  $chr = ($chr === false) ? '' : $chr;
253  }
254  return sprintf('\\u%04s', strtoupper(bin2hex($chr)));
255  },
256  $string
257  );
258  }
259 
267  public function escapeCss($string)
268  {
269  return $this->getEscaper()->escapeCss($string);
270  }
271 
280  public function escapeJsQuote($data, $quote = '\'')
281  {
282  if (is_array($data)) {
283  $result = [];
284  foreach ($data as $item) {
285  $result[] = $this->escapeJsQuote($item, $quote);
286  }
287  } else {
288  $result = str_replace($quote, '\\' . $quote, (string)$data);
289  }
290  return $result;
291  }
292 
300  public function escapeXssInUrl($data)
301  {
302  return htmlspecialchars(
303  $this->escapeScriptIdentifiers((string)$data),
304  ENT_COMPAT | ENT_HTML5 | ENT_HTML401,
305  'UTF-8',
306  false
307  );
308  }
309 
316  private function escapeScriptIdentifiers(string $data): string
317  {
318  $filteredData = preg_replace(self::$xssFiltrationPattern, ':', $data) ?: '';
319  if (preg_match(self::$xssFiltrationPattern, $filteredData)) {
320  $filteredData = $this->escapeScriptIdentifiers($filteredData);
321  }
322 
323  return $filteredData;
324  }
325 
336  public function escapeQuote($data, $addSlashes = false)
337  {
338  if ($addSlashes === true) {
339  $data = addslashes($data);
340  }
341  return htmlspecialchars($data, ENT_QUOTES, null, false);
342  }
343 
350  private function getEscaper()
351  {
352  if ($this->escaper == null) {
354  ->get(\Magento\Framework\ZendEscaper::class);
355  }
356  return $this->escaper;
357  }
358 
365  private function getLogger()
366  {
367  if ($this->logger == null) {
369  ->get(\Psr\Log\LoggerInterface::class);
370  }
371  return $this->logger;
372  }
373 
380  private function filterProhibitedTags(array $allowedTags): array
381  {
382  $notAllowedTags = array_intersect(
383  array_map('strtolower', $allowedTags),
384  $this->notAllowedTags
385  );
386 
387  if (!empty($notAllowedTags)) {
388  $this->getLogger()->critical(
389  'The following tag(s) are not allowed: ' . implode(', ', $notAllowedTags)
390  );
391  $allowedTags = array_diff($allowedTags, $this->notAllowedTags);
392  }
393 
394  return $allowedTags;
395  }
396 }
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
escapeJsQuote($data, $quote='\'')
Definition: Escaper.php:280
$quote
escapeHtml($data, $allowedTags=null)
Definition: Escaper.php:60
$value
Definition: gender.phtml:16
escapeQuote($data, $addSlashes=false)
Definition: Escaper.php:336
escapeHtmlAttr($string, $escapeSingleQuote=true)
Definition: Escaper.php:202
if(!isset($_GET['name'])) $name
Definition: log.php:14