Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 
 

186 строки
6.5 KiB

  1. /**
  2. * Copyright © Magento, Inc. All rights reserved.
  3. * See COPYING.txt for license details.
  4. */
  5. /**
  6. * A loose JavaScript version of Magento\Framework\Escaper
  7. *
  8. * Due to differences in how XML/HTML is processed in PHP vs JS there are a couple of minor differences in behavior
  9. * from the PHP counterpart.
  10. *
  11. * The first difference is that the default invocation of escapeHtml without allowedTags will double-escape existing
  12. * entities as the intention of such an invocation is that the input isn't supposed to contain any HTML.
  13. *
  14. * The second difference is that escapeHtml will not escape quotes. Since the input is actually being processed by the
  15. * DOM there is no chance of quotes being mixed with HTML syntax. And, since escapeHtml is not
  16. * intended to be used with raw injection into a HTML attribute, this is acceptable.
  17. *
  18. * @api
  19. */
  20. define([], function () {
  21. 'use strict';
  22. return {
  23. neverAllowedElements: ['script', 'img', 'embed', 'iframe', 'video', 'source', 'object', 'audio'],
  24. generallyAllowedAttributes: ['id', 'class', 'href', 'title', 'style'],
  25. forbiddenAttributesByElement: {
  26. a: ['style']
  27. },
  28. /**
  29. * Escape a string for safe injection into HTML
  30. *
  31. * @param {String} data
  32. * @param {Array|null} allowedTags
  33. * @returns {String}
  34. */
  35. escapeHtml: function (data, allowedTags) {
  36. var domParser = new DOMParser(),
  37. fragment = domParser.parseFromString('<div></div>', 'text/html');
  38. fragment = fragment.body.childNodes[0];
  39. allowedTags = typeof allowedTags === 'object' && allowedTags.length ? allowedTags : null;
  40. if (allowedTags) {
  41. fragment.innerHTML = data || '';
  42. allowedTags = this._filterProhibitedTags(allowedTags);
  43. this._removeComments(fragment);
  44. this._removeNotAllowedElements(fragment, allowedTags);
  45. this._removeNotAllowedAttributes(fragment);
  46. return fragment.innerHTML;
  47. }
  48. fragment.textContent = data || '';
  49. return fragment.innerHTML;
  50. },
  51. /**
  52. * Remove the always forbidden tags from a list of provided tags
  53. *
  54. * @param {Array} tags
  55. * @returns {Array}
  56. * @private
  57. */
  58. _filterProhibitedTags: function (tags) {
  59. return tags.filter(function (n) {
  60. return this.neverAllowedElements.indexOf(n) === -1;
  61. }.bind(this));
  62. },
  63. /**
  64. * Remove comment nodes from the given node
  65. *
  66. * @param {Node} node
  67. * @private
  68. */
  69. _removeComments: function (node) {
  70. var treeWalker = node.ownerDocument.createTreeWalker(
  71. node,
  72. NodeFilter.SHOW_COMMENT,
  73. function () {
  74. return NodeFilter.FILTER_ACCEPT;
  75. },
  76. false
  77. ),
  78. nodesToRemove = [];
  79. while (treeWalker.nextNode()) {
  80. nodesToRemove.push(treeWalker.currentNode);
  81. }
  82. nodesToRemove.forEach(function (nodeToRemove) {
  83. nodeToRemove.parentNode.removeChild(nodeToRemove);
  84. });
  85. },
  86. /**
  87. * Strip the given node of all disallowed tags while permitting any nested text nodes
  88. *
  89. * @param {Node} node
  90. * @param {Array|null} allowedTags
  91. * @private
  92. */
  93. _removeNotAllowedElements: function (node, allowedTags) {
  94. var treeWalker = node.ownerDocument.createTreeWalker(
  95. node,
  96. NodeFilter.SHOW_ELEMENT,
  97. function (currentNode) {
  98. return allowedTags.indexOf(currentNode.nodeName.toLowerCase()) === -1 ?
  99. NodeFilter.FILTER_ACCEPT
  100. // SKIP instead of REJECT because REJECT also rejects child nodes
  101. : NodeFilter.FILTER_SKIP;
  102. },
  103. false
  104. ),
  105. nodesToRemove = [];
  106. while (treeWalker.nextNode()) {
  107. if (allowedTags.indexOf(treeWalker.currentNode.nodeName.toLowerCase()) === -1) {
  108. nodesToRemove.push(treeWalker.currentNode);
  109. }
  110. }
  111. nodesToRemove.forEach(function (nodeToRemove) {
  112. nodeToRemove.parentNode.replaceChild(
  113. node.ownerDocument.createTextNode(nodeToRemove.textContent),
  114. nodeToRemove
  115. );
  116. });
  117. },
  118. /**
  119. * Remove any invalid attributes from the given node
  120. *
  121. * @param {Node} node
  122. * @private
  123. */
  124. _removeNotAllowedAttributes: function (node) {
  125. var treeWalker = node.ownerDocument.createTreeWalker(
  126. node,
  127. NodeFilter.SHOW_ELEMENT,
  128. function () {
  129. return NodeFilter.FILTER_ACCEPT;
  130. },
  131. false
  132. ),
  133. i,
  134. attribute,
  135. nodeName,
  136. attributesToRemove = [];
  137. while (treeWalker.nextNode()) {
  138. for (i = 0; i < treeWalker.currentNode.attributes.length; i++) {
  139. attribute = treeWalker.currentNode.attributes[i];
  140. nodeName = treeWalker.currentNode.nodeName.toLowerCase();
  141. if (this.generallyAllowedAttributes.indexOf(attribute.name) === -1 || // eslint-disable-line max-depth,max-len
  142. this._checkHrefValue(attribute) ||
  143. this.forbiddenAttributesByElement[nodeName] &&
  144. this.forbiddenAttributesByElement[nodeName].indexOf(attribute.name) !== -1
  145. ) {
  146. attributesToRemove.push(attribute);
  147. }
  148. }
  149. }
  150. attributesToRemove.forEach(function (attributeToRemove) {
  151. attributeToRemove.ownerElement.removeAttribute(attributeToRemove.name);
  152. });
  153. },
  154. /**
  155. * Check that attribute contains script content
  156. *
  157. * @param {Object} attribute
  158. * @private
  159. */
  160. _checkHrefValue: function (attribute) {
  161. return attribute.nodeName === 'href' && attribute.nodeValue.startsWith('javascript');
  162. }
  163. };
  164. });