|
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
-
- /**
- * A loose JavaScript version of Magento\Framework\Escaper
- *
- * Due to differences in how XML/HTML is processed in PHP vs JS there are a couple of minor differences in behavior
- * from the PHP counterpart.
- *
- * The first difference is that the default invocation of escapeHtml without allowedTags will double-escape existing
- * entities as the intention of such an invocation is that the input isn't supposed to contain any HTML.
- *
- * The second difference is that escapeHtml will not escape quotes. Since the input is actually being processed by the
- * DOM there is no chance of quotes being mixed with HTML syntax. And, since escapeHtml is not
- * intended to be used with raw injection into a HTML attribute, this is acceptable.
- *
- * @api
- */
- define([], function () {
- 'use strict';
-
- return {
- neverAllowedElements: ['script', 'img', 'embed', 'iframe', 'video', 'source', 'object', 'audio'],
- generallyAllowedAttributes: ['id', 'class', 'href', 'title', 'style'],
- forbiddenAttributesByElement: {
- a: ['style']
- },
-
- /**
- * Escape a string for safe injection into HTML
- *
- * @param {String} data
- * @param {Array|null} allowedTags
- * @returns {String}
- */
- escapeHtml: function (data, allowedTags) {
- var domParser = new DOMParser(),
- fragment = domParser.parseFromString('<div></div>', 'text/html');
-
- fragment = fragment.body.childNodes[0];
- allowedTags = typeof allowedTags === 'object' && allowedTags.length ? allowedTags : null;
-
- if (allowedTags) {
- fragment.innerHTML = data || '';
- allowedTags = this._filterProhibitedTags(allowedTags);
-
- this._removeComments(fragment);
- this._removeNotAllowedElements(fragment, allowedTags);
- this._removeNotAllowedAttributes(fragment);
-
- return fragment.innerHTML;
- }
-
- fragment.textContent = data || '';
-
- return fragment.innerHTML;
- },
-
- /**
- * Remove the always forbidden tags from a list of provided tags
- *
- * @param {Array} tags
- * @returns {Array}
- * @private
- */
- _filterProhibitedTags: function (tags) {
- return tags.filter(function (n) {
- return this.neverAllowedElements.indexOf(n) === -1;
- }.bind(this));
- },
-
- /**
- * Remove comment nodes from the given node
- *
- * @param {Node} node
- * @private
- */
- _removeComments: function (node) {
- var treeWalker = node.ownerDocument.createTreeWalker(
- node,
- NodeFilter.SHOW_COMMENT,
- function () {
- return NodeFilter.FILTER_ACCEPT;
- },
- false
- ),
- nodesToRemove = [];
-
- while (treeWalker.nextNode()) {
- nodesToRemove.push(treeWalker.currentNode);
- }
-
- nodesToRemove.forEach(function (nodeToRemove) {
- nodeToRemove.parentNode.removeChild(nodeToRemove);
- });
- },
-
- /**
- * Strip the given node of all disallowed tags while permitting any nested text nodes
- *
- * @param {Node} node
- * @param {Array|null} allowedTags
- * @private
- */
- _removeNotAllowedElements: function (node, allowedTags) {
- var treeWalker = node.ownerDocument.createTreeWalker(
- node,
- NodeFilter.SHOW_ELEMENT,
- function (currentNode) {
- return allowedTags.indexOf(currentNode.nodeName.toLowerCase()) === -1 ?
- NodeFilter.FILTER_ACCEPT
- // SKIP instead of REJECT because REJECT also rejects child nodes
- : NodeFilter.FILTER_SKIP;
- },
- false
- ),
- nodesToRemove = [];
-
- while (treeWalker.nextNode()) {
- if (allowedTags.indexOf(treeWalker.currentNode.nodeName.toLowerCase()) === -1) {
- nodesToRemove.push(treeWalker.currentNode);
- }
- }
-
- nodesToRemove.forEach(function (nodeToRemove) {
- nodeToRemove.parentNode.replaceChild(
- node.ownerDocument.createTextNode(nodeToRemove.textContent),
- nodeToRemove
- );
- });
- },
-
- /**
- * Remove any invalid attributes from the given node
- *
- * @param {Node} node
- * @private
- */
- _removeNotAllowedAttributes: function (node) {
- var treeWalker = node.ownerDocument.createTreeWalker(
- node,
- NodeFilter.SHOW_ELEMENT,
- function () {
- return NodeFilter.FILTER_ACCEPT;
- },
- false
- ),
- i,
- attribute,
- nodeName,
- attributesToRemove = [];
-
- while (treeWalker.nextNode()) {
- for (i = 0; i < treeWalker.currentNode.attributes.length; i++) {
- attribute = treeWalker.currentNode.attributes[i];
- nodeName = treeWalker.currentNode.nodeName.toLowerCase();
-
- if (this.generallyAllowedAttributes.indexOf(attribute.name) === -1 || // eslint-disable-line max-depth,max-len
- this._checkHrefValue(attribute) ||
- this.forbiddenAttributesByElement[nodeName] &&
- this.forbiddenAttributesByElement[nodeName].indexOf(attribute.name) !== -1
- ) {
- attributesToRemove.push(attribute);
- }
- }
- }
-
- attributesToRemove.forEach(function (attributeToRemove) {
- attributeToRemove.ownerElement.removeAttribute(attributeToRemove.name);
- });
- },
-
- /**
- * Check that attribute contains script content
- *
- * @param {Object} attribute
- * @private
- */
- _checkHrefValue: function (attribute) {
- return attribute.nodeName === 'href' && attribute.nodeValue.startsWith('javascript');
- }
- };
- });
|