TINY-11332: Update dependencies (#9909)

* TINY-11332: Update dependencies and tests

* TINY-11332: Update cahngelog, update comments, add test
This commit is contained in:
jscasca
2024-10-10 10:50:28 +11:00
committed by GitHub
parent 49ce762645
commit 44f3a8082a
10 changed files with 136 additions and 99 deletions

View File

@ -0,0 +1,6 @@
project: tinymce
kind: Fixed
body: 'Invalid HTML elements within SVG elements were not removed.'
time: 2024-10-08T17:17:19.103070749+11:00
custom:
Issue: TINY-11332

View File

@ -33,6 +33,6 @@
"@ephox/darwin": "^9.0.0",
"@tinymce/oxide": "^3.0.0",
"@tinymce/oxide-icons-default": "^3.0.0",
"dompurify": "3.0.5"
"dompurify": "3.1.7"
}
}

View File

@ -178,14 +178,17 @@ const setupPurify = (settings: DomParserSettings, schema: Schema, namespaceTrack
};
const getPurifyConfig = (settings: DomParserSettings, mimeType: string): Config => {
const basePurifyConfig: Config = {
// Current dompurify types only cover up to 3.0.5 which does not include this new setting
const basePurifyConfig: Config & { SAFE_FOR_XML: boolean } = {
IN_PLACE: true,
ALLOW_UNKNOWN_PROTOCOLS: true,
// Deliberately ban all tags and attributes by default, and then un-ban them on demand in hooks
// #comment and #cdata-section are always allowed as they aren't controlled via the schema
// body is also allowed due to the DOMPurify checking the root node before sanitizing
ALLOWED_TAGS: [ '#comment', '#cdata-section', 'body' ],
ALLOWED_ATTR: []
ALLOWED_ATTR: [],
// TINY-11332: New settings for dompurify 3.1.7
SAFE_FOR_XML: false
};
const config = { ...basePurifyConfig };

View File

@ -11,12 +11,20 @@ describe('browser.tinymce.core.content.insert.NamespaceElementsTest', () => {
valid_children: '+p[math]'
}, [], true);
it('TINY-10237: Inserting SVG elements but filter out things like scripts', () => {
it('TINY-10237: Inserting SVG elements but filter out things like scripts and invalid children', () => {
const editor = hook.editor();
editor.setContent('<p>ab</p>');
TinySelections.setCursor(editor, [ 0, 0 ], 1);
editor.insertContent('<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc><script>alert(1)</script><p>hello</p></circle></a></svg>');
TinyAssertions.assertContent(editor, '<p>a<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc><p>hello</p></desc></circle></svg>b</p>');
TinyAssertions.assertContent(editor, '<p>a<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc></desc></circle></svg>b</p>');
});
it('TINY-11332: Inserting SVG elements but filter out things like scripts keeping content', () => {
const editor = hook.editor();
editor.setContent('<p>ab</p>');
TinySelections.setCursor(editor, [ 0, 0 ], 1);
editor.insertContent('<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc><script>alert(1)</script>hello</circle></a></svg>');
TinyAssertions.assertContent(editor, '<p>a<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc>hello</desc></circle></svg>b</p>');
});
it('TINY-10809: Inserting math elements but filter out things like scripts', () => {

View File

@ -1665,12 +1665,21 @@ describe('browser.tinymce.core.html.DomParserTest', () => {
assert.equal(serializedHtml, '<svg></svg><p>foo</p>');
});
it('TINY-10237: Should retain SVG elements as is but filter out scripts', () => {
// Updated for TINY-11332: Remove html elements inside SVG
it('TINY-10237: Should retain SVG elements as is but filter out scripts and invalid children', () => {
const schema = Schema();
schema.addValidElements('svg[*]');
const input = '<svg><circle><desc><b>foo</b><script>alert(1)</script></desc></circle></svg>foo';
const serializedHtml = HtmlSerializer({}, schema).serialize(DomParser({ forced_root_block: 'p' }, schema).parse(input));
assert.equal(serializedHtml, '<svg><circle><desc><b>foo</b></desc></circle></svg><p>foo</p>');
assert.equal(serializedHtml, '<svg><circle><desc></desc></circle></svg><p>foo</p>');
});
it('TINY-11332: Should retain SVG elements and keep HTML elements that are valid inside an SVG', () => {
const schema = Schema();
schema.addValidElements('svg[*]');
const input = '<svg><a href="/docs/Web/SVG/Element/circle"><circle cx="50" cy="40" r="35" /></a><script>alert(1)</script></svg>foo';
const serializedHtml = HtmlSerializer({}, schema).serialize(DomParser({ forced_root_block: 'p' }, schema).parse(input));
assert.equal(serializedHtml, '<svg><a href="/docs/Web/SVG/Element/circle"><circle cx="50" cy="40" r="35"></circle></a></svg><p>foo</p>');
});
it('TINY-10237: Should retain SVG elements and keep scripts if sanitize is set to false', () => {
@ -1739,31 +1748,6 @@ describe('browser.tinymce.core.html.DomParserTest', () => {
assert.equal(serializedHtml, '<div><math> <mrow> </mrow> </math> <math> </math></div>');
});
it('TINY-11166: Allow custom annotation mime types only annotation with encoding and no src attributes', () => {
const schema = Schema();
schema.addValidElements('math[*]');
const input = [
'<math><annotation encoding="application/x-tex">\\frac{1}{2}</annotation></math>',
'<math><annotation encoding="application/custom">custom</annotation></math>',
'<math><annotation encoding="application/custom" src="foo">custom with src</annotation></math>',
'<math><annotation encoding="wiris">{"version":"1.1","math":"&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;/math&gt;"}</annotation></math>',
'<math><annotation encoding="text/html">html</annotation></math>',
'<math><annotation encoding="text/svg">svg</annotation></math>',
].join('');
const allowedAnnotationEncodings = [ 'application/x-tex', 'application/custom', 'wiris' ];
const domParser = DomParser({ forced_root_block: 'p', allow_mathml_annotation_encodings: allowedAnnotationEncodings }, schema);
const serializedHtml = HtmlSerializer({}, schema).serialize(domParser.parse(input));
const expected = [
'<math><annotation encoding="application/x-tex">\\frac{1}{2}</annotation></math>',
'<math><annotation encoding="application/custom">custom</annotation></math>',
'<math><annotation encoding="application/custom">custom with src</annotation></math>',
'<math><annotation encoding="wiris">{"version":"1.1","math":"&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mfrac&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/mfrac&gt;&lt;/math&gt;"}</annotation></math>',
'<math>html</math>',
'<math>svg</math>'
].join('');
assert.equal(serializedHtml, expected);
});
});
context('Special elements', () => {

View File

@ -55,7 +55,7 @@ describe('browser.tinymce.core.html.SanitizationTest', () => {
it('Sanitize SVG with mixed HTML', () => testNamespaceSanitizer({
input: '<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc><script>alert(1)</script><p>hello</p></circle></a></svg>',
expected: '<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc><p>hello</p></desc></circle></svg>'
expected: '<svg><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"><desc></desc></circle></svg>'
}));
it('Sanitize SVG with xlink with script url', () => testNamespaceSanitizer({

View File

@ -17,7 +17,8 @@ describe('browser.tinymce.core.html.SerializerTest', () => {
);
assert.equal(serializer.serialize(DomParser().parse('<!-- comment -->')), '<!-- comment -->');
assert.equal(serializer.serialize(DomParser().parse('<![CDATA[cdata]]>', { format: 'xhtml' })), '<![CDATA[cdata]]>');
assert.equal(serializer.serialize(DomParser().parse('<?xml-stylesheet attr="value" ?>', { format: 'xhtml' })), '<?xml-stylesheet attr="value" ?>');
// TINY-11332: Dompurify removes PI to avoid bypassing parsing. Keep this in case of future reference
// assert.equal(serializer.serialize(DomParser().parse('<?xml-stylesheet attr="value" ?>', { format: 'xhtml' })), '<?xml-stylesheet attr="value" ?>');
});
it('Sorting of attributes', () => {

View File

@ -43,7 +43,7 @@
"@tinymce/eslint-plugin": "^2.3.0",
"@tinymce/moxiedoc": "^0.3.0",
"@types/chai": "^4.3.3",
"@types/dompurify": "3.0.2",
"@types/dompurify": "3.0.5",
"@types/prismjs": "^1.16.6",
"chai": "^4.4.1",
"chalk": "^4.1.0",

View File

@ -1,47 +1,52 @@
diff --git a/node_modules/dompurify/dist/purify.es.js b/node_modules/dompurify/dist/purify.es.js
index ee9246e..d297e22 100644
--- a/node_modules/dompurify/dist/purify.es.js
+++ b/node_modules/dompurify/dist/purify.es.js
@@ -1190,6 +1190,7 @@ function createDOMPurify() {
namespaceURI
diff --git a/node_modules/dompurify/dist/purify.es.mjs b/node_modules/dompurify/dist/purify.es.mjs
index 7a42ce6..3a0a69b 100644
--- a/node_modules/dompurify/dist/purify.es.mjs
+++ b/node_modules/dompurify/dist/purify.es.mjs
@@ -1169,6 +1169,7 @@ function createDOMPurify() {
} = attr;
value = name === 'value' ? attr.value : stringTrim(attr.value);
const lcName = transformCaseFunc(name);
let value = name === 'value' ? attrValue : stringTrim(attrValue);
+ const initValue = value;
lcName = transformCaseFunc(name);
/* Execute a hook if present */
hookEvent.attrName = lcName;
@@ -1184,10 +1185,10 @@ function createDOMPurify() {
}
@@ -1209,11 +1210,11 @@ function createDOMPurify() {
/* Remove attribute */
- _removeAttribute(name, currentNode);
/* Did the hooks approve of the attribute? */
if (!hookEvent.keepAttr) {
+ _removeAttribute(name, currentNode);
continue;
}
/* Work around a security issue in jQuery 3.0 */
@@ -1238,6 +1239,7 @@ function createDOMPurify() {
const lcTag = transformCaseFunc(currentNode.nodeName);
@@ -1207,6 +1208,7 @@ function createDOMPurify() {
/* Is `value` valid for this attribute? */
const lcTag = transformCaseFunc(currentNode.nodeName);
if (!_isValidAttribute(lcTag, lcName, value)) {
+ _removeAttribute(name, currentNode);
continue;
}
/* Full DOM Clobbering protection via namespace isolation,
@@ -1274,17 +1276,18 @@ function createDOMPurify() {
}
/* Handle invalid data-* attribute set by try-catching it */
-
@@ -1246,19 +1248,21 @@ function createDOMPurify() {
}
/* Handle invalid data-* attribute set by try-catching it */
- try {
- if (namespaceURI) {
- currentNode.setAttributeNS(namespaceURI, name, value);
- } else {
- /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
- currentNode.setAttribute(name, value);
- }
- if (_isClobbered(currentNode)) {
- _forceRemove(currentNode);
- } else {
- arrayPop(DOMPurify.removed);
- }
- } catch (_) {}
+ if (value !== initValue) {
+ try {
+ if (namespaceURI) {
@ -50,60 +55,65 @@ index ee9246e..d297e22 100644
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
+ currentNode.setAttribute(name, value);
+ }
+ } catch (_) {
+ _removeAttribute(name, currentNode);
}
-
- arrayPop(DOMPurify.removed);
- } catch (_) {}
+ if (_isClobbered(currentNode)) {
+ _forceRemove(currentNode);
+ } else {
+ arrayPop(DOMPurify.removed);
+ }
+ } catch (_) {}
+ }
}
/* Execute a hook if present */
/* Execute a hook if present */
diff --git a/node_modules/dompurify/dist/purify.js b/node_modules/dompurify/dist/purify.js
index ba807e1..c6512fc 100644
index 5b07950..2a00fff 100644
--- a/node_modules/dompurify/dist/purify.js
+++ b/node_modules/dompurify/dist/purify.js
@@ -1196,6 +1196,7 @@
namespaceURI
@@ -1175,6 +1175,7 @@
} = attr;
value = name === 'value' ? attr.value : stringTrim(attr.value);
const lcName = transformCaseFunc(name);
let value = name === 'value' ? attrValue : stringTrim(attrValue);
+ const initValue = value;
lcName = transformCaseFunc(name);
/* Execute a hook if present */
hookEvent.attrName = lcName;
@@ -1190,10 +1191,10 @@
}
@@ -1215,11 +1216,10 @@
/* Remove attribute */
- _removeAttribute(name, currentNode);
/* Did the hooks approve of the attribute? */
-
/* Did the hooks approve of the attribute? */
if (!hookEvent.keepAttr) {
+ _removeAttribute(name, currentNode);
continue;
}
/* Work around a security issue in jQuery 3.0 */
@@ -1244,6 +1244,7 @@
const lcTag = transformCaseFunc(currentNode.nodeName);
@@ -1213,6 +1214,7 @@
/* Is `value` valid for this attribute? */
const lcTag = transformCaseFunc(currentNode.nodeName);
if (!_isValidAttribute(lcTag, lcName, value)) {
+ _removeAttribute(name, currentNode);
continue;
}
/* Full DOM Clobbering protection via namespace isolation,
@@ -1280,17 +1281,18 @@
}
/* Handle invalid data-* attribute set by try-catching it */
-
@@ -1252,19 +1254,21 @@
}
/* Handle invalid data-* attribute set by try-catching it */
- try {
- if (namespaceURI) {
- currentNode.setAttributeNS(namespaceURI, name, value);
- } else {
- /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
- currentNode.setAttribute(name, value);
- }
- if (_isClobbered(currentNode)) {
- _forceRemove(currentNode);
- } else {
- arrayPop(DOMPurify.removed);
- }
- } catch (_) {}
+ if (value !== initValue) {
+ try {
+ if (namespaceURI) {
@ -112,13 +122,13 @@ index ba807e1..c6512fc 100644
+ /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
+ currentNode.setAttribute(name, value);
+ }
+ } catch (_) {
+ _removeAttribute(name, currentNode);
}
-
- arrayPop(DOMPurify.removed);
- } catch (_) {}
+ if (_isClobbered(currentNode)) {
+ _forceRemove(currentNode);
+ } else {
+ arrayPop(DOMPurify.removed);
+ }
+ } catch (_) {}
+ }
}
/* Execute a hook if present */
/* Execute a hook if present */

View File

@ -2205,10 +2205,10 @@
dependencies:
"@types/node" "*"
"@types/dompurify@3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.2.tgz#c1cd33a475bc49c43c2a7900e41028e2136a4553"
integrity sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==
"@types/dompurify@3.0.5":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7"
integrity sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==
dependencies:
"@types/trusted-types" "*"
@ -4614,10 +4614,10 @@ domhandler@^4.2.0, domhandler@^4.3.1:
dependencies:
domelementtype "^2.2.0"
dompurify@3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.5.tgz#eb3d9cfa10037b6e73f32c586682c4b2ab01fbed"
integrity sha512-F9e6wPGtY+8KNMRAVfxeCOHU0/NPWMSENNq4pQctuXRqqdEPW7q3CrLbR5Nse044WwacyjHGOMlvNsBe1y6z9A==
dompurify@3.1.7:
version "3.1.7"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.7.tgz#711a8c96479fb6ced93453732c160c3c72418a6a"
integrity sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==
domutils@^2.8.0:
version "2.8.0"
@ -10829,7 +10829,16 @@ string-template@~0.2.1:
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -10902,7 +10911,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -10916,6 +10925,13 @@ strip-ansi@^3.0.0:
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@ -12095,7 +12111,7 @@ wordwrapjs@^4.0.0:
reduce-flatten "^2.0.0"
typical "^5.2.0"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -12113,6 +12129,15 @@ wrap-ansi@^6.0.1:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"