Skip to content

Commit c523152

Browse files
authored
Merge pull request #5252 from magento-borg/borg-2.4.0
[CIA] Bug fixes
2 parents e759fa5 + bd58b08 commit c523152

File tree

12 files changed

+415
-12
lines changed

12 files changed

+415
-12
lines changed

app/code/Magento/Sales/Test/Mftf/Test/AdminUnassignCustomOrderStatusTest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555

5656
<!--Click unassign and verify AssertOrderStatusSuccessUnassignMessage-->
5757
<click selector="{{AdminOrderStatusGridSection.unassign}}" stepKey="clickUnassign"/>
58-
<see selector="{{AdminMessagesSection.success}}" userInput="You have unassigned the order status." stepKey="seeAssertOrderStatusSuccessUnassignMessage"/>
58+
<waitForText selector="{{AdminMessagesSection.success}}" userInput="You have unassigned the order status." stepKey="seeAssertOrderStatusSuccessUnassignMessage"/>
5959

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

app/code/Magento/Theme/Controller/Result/MessagePlugin.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
/**
1616
* Plugin for putting messages to cookies
17+
*
18+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
1719
*/
1820
class MessagePlugin
1921
{
@@ -116,7 +118,6 @@ public function afterRenderResult(
116118
* ],
117119
* ]
118120
*
119-
*
120121
* @param array $messages List of Magento messages that must be set as 'mage-messages' cookie.
121122
* @return void
122123
*/

app/code/Magento/Theme/view/frontend/templates/messages.phtml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,18 @@
1111
class: 'message-' + message.type + ' ' + message.type + ' message',
1212
'data-ui-id': 'message-' + message.type
1313
}">
14-
<div data-bind="html: message.text"></div>
14+
<div data-bind="html: $parent.prepareMessageForHtml(message.text)"></div>
1515
</div>
1616
</div>
1717
<!-- /ko -->
18+
1819
<!-- ko if: messages().messages && messages().messages.length > 0 -->
1920
<div role="alert" data-bind="foreach: { data: messages().messages, as: 'message' }" class="messages">
2021
<div data-bind="attr: {
2122
class: 'message-' + message.type + ' ' + message.type + ' message',
2223
'data-ui-id': 'message-' + message.type
2324
}">
24-
<div data-bind="html: message.text"></div>
25+
<div data-bind="html: $parent.prepareMessageForHtml(message.text)"></div>
2526
</div>
2627
</div>
2728
<!-- /ko -->

app/code/Magento/Theme/view/frontend/web/js/view/messages.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ define([
1111
'uiComponent',
1212
'Magento_Customer/js/customer-data',
1313
'underscore',
14+
'escaper',
1415
'jquery/jquery-storageapi'
15-
], function ($, Component, customerData, _) {
16+
], function ($, Component, customerData, _, escaper) {
1617
'use strict';
1718

1819
return Component.extend({
1920
defaults: {
2021
cookieMessages: [],
21-
messages: []
22+
messages: [],
23+
allowedTags: ['div', 'span', 'b', 'strong', 'i', 'em', 'u', 'a']
2224
},
2325

2426
/**
@@ -38,6 +40,16 @@ define([
3840
}
3941

4042
$.cookieStorage.set('mage-messages', '');
43+
},
44+
45+
/**
46+
* Prepare the given message to be rendered as HTML
47+
*
48+
* @param {String} message
49+
* @return {String}
50+
*/
51+
prepareMessageForHtml: function (message) {
52+
return escaper.escapeHtml(message, this.allowedTags);
4153
}
4254
});
4355
});

0 commit comments

Comments
 (0)