changeset 72fd59da9505 in sao:5.0
details: https://hg.tryton.org/sao?cmd=changeset;node=72fd59da9505
description:
Sanitize RichtText fields content
issue9405
review327451002
(grafted from 4e0e93b11cad63c6b25f5230055653edb21a334c)
diffstat:
CHANGELOG | 1 +
COPYRIGHT | 1 +
Gruntfile.js | 3 +-
src/html_sanitizer.js | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/view/form.js | 5 +-
tests/sao.js | 19 +++++++++
6 files changed, 131 insertions(+), 3 deletions(-)
diffs (192 lines):
diff -r 19a307dc7455 -r 72fd59da9505 CHANGELOG
--- a/CHANGELOG Mon Jun 29 17:29:45 2020 +0200
+++ b/CHANGELOG Mon Jun 29 17:33:06 2020 +0200
@@ -1,3 +1,4 @@
+* Sanitize RichtText fields content (issue9405)
* Escape external string (issue9394)
Version 5.0.25 - 2020-06-16
diff -r 19a307dc7455 -r 72fd59da9505 COPYRIGHT
--- a/COPYRIGHT Mon Jun 29 17:29:45 2020 +0200
+++ b/COPYRIGHT Mon Jun 29 17:33:06 2020 +0200
@@ -2,6 +2,7 @@
Copyright (C) 2012-2020 Cédric Krier.
Copyright (C) 2012-2014 Bertrand Chenal.
Copyright (C) 2012-2020 B2CK SPRL.
+Copyright (C) 2019 Jitbit.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
diff -r 19a307dc7455 -r 72fd59da9505 Gruntfile.js
--- a/Gruntfile.js Mon Jun 29 17:29:45 2020 +0200
+++ b/Gruntfile.js Mon Jun 29 17:33:06 2020 +0200
@@ -21,7 +21,8 @@
'src/window.js',
'src/wizard.js',
'src/board.js',
- 'src/bus.js'
+ 'src/bus.js',
+ 'src/html_sanitizer.js'
];
// Project configuration.
diff -r 19a307dc7455 -r 72fd59da9505 src/html_sanitizer.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/html_sanitizer.js Mon Jun 29 17:33:06 2020 +0200
@@ -0,0 +1,105 @@
+/*
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+(function () {
+ 'use strict';
+
+ var tag_whitelist = {
+ B: true,
+ BODY: true,
+ BR: true,
+ DIV: true,
+ FONT: true,
+ I: true,
+ U: true,
+ };
+
+ var attribute_whitelist = {
+ align: true,
+ color: true,
+ face: true,
+ size: true,
+ };
+
+ Sao.HtmlSanitizer = {};
+ Sao.HtmlSanitizer.sanitize = function(input) {
+ input = input.trim();
+ // to save performance and not create iframe
+ if (input === "") return "";
+
+ // firefox "bogus node" workaround
+ if (input == "<br>") return "";
+
+ var iframe = document.createElement('iframe');
+ if (iframe.sandbox === undefined) {
+ // Browser does not support sandboxed iframes
+ console.warn("Your browser do not support sandboxed iframes," +
+ " unable to sanitize HTML.");
+ return input;
+ }
+ iframe.sandbox = 'allow-same-origin';
+ iframe.style.display = 'none';
+ // necessary so the iframe contains a document
+ document.body.appendChild(iframe);
+ var iframedoc = (iframe.contentDocument ||
+ iframe.contentWindow.document);
+ // null in IE
+ if (iframedoc.body === null) {
+ iframedoc.write("<body></body>");
+ }
+ iframedoc.body.innerHTML = input;
+
+ function make_sanitized_copy(node) {
+ var new_node;
+ if (node.nodeType == Node.TEXT_NODE) {
+ new_node = node.cloneNode(true);
+ } else if (node.nodeType == Node.ELEMENT_NODE &&
+ tag_whitelist[node.tagName]) {
+ //remove useless empty tags
+ if ((node.tagName != "BR") && node.innerHTML.trim() === "") {
+ return document.createDocumentFragment();
+ }
+
+ new_node = iframedoc.createElement(node.tagName);
+
+ for (var i = 0; i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ if (attribute_whitelist[attr.name]) {
+ new_node.setAttribute(attr.name, attr.value);
+ }
+ }
+ for (i = 0; i < node.childNodes.length; i++) {
+ var sub_copy = make_sanitized_copy(node.childNodes[i]);
+ new_node.appendChild(sub_copy, false);
+ }
+ } else {
+ new_node = document.createDocumentFragment();
+ }
+ return new_node;
+ }
+
+ var result_element = make_sanitized_copy(iframedoc.body);
+ document.body.removeChild(iframe);
+ // replace is just for cleaner code
+ return result_element.innerHTML
+ .replace(/<br[^>]*>(\S)/g, "<br>\n$1")
+ .replace(/div><div/g, "div>\n<div");
+ };
+})();
diff -r 19a307dc7455 -r 72fd59da9505 src/view/form.js
--- a/src/view/form.js Mon Jun 29 17:29:45 2020 +0200
+++ b/src/view/form.js Mon Jun 29 17:33:06 2020 +0200
@@ -2069,7 +2069,7 @@
this.input.attr('spellcheck', 'true');
}
}
- this.input.html(value);
+ this.input.html(Sao.HtmlSanitizer.sanitize(value || ''));
},
focus: function() {
this.input.focus();
@@ -2087,7 +2087,8 @@
field.set_client(record, value);
},
_normalize_markup: function(content) {
- var el = jQuery('<div/>').html(content || '');
+ var el = jQuery('<div/>').html(
+ Sao.HtmlSanitizer.sanitize(content || ''));
this._normalize(el);
return el.html();
},
diff -r 19a307dc7455 -r 72fd59da9505 tests/sao.js
--- a/tests/sao.js Mon Jun 29 17:29:45 2020 +0200
+++ b/tests/sao.js Mon Jun 29 17:33:06 2020 +0200
@@ -2494,6 +2494,25 @@
});
});
+ QUnit.test('HTML Sanitization', function() {
+ var examples = [
+ ["Test", "Test"],
+ ["<b>Test</b>", "<b>Test</b>"],
+ ["<div><b>Test</b></div>", "<div><b>Test</b></div>"],
+ ["<script>window.alert('insecure')</script>", ""],
+ ["<b><script>window.alert('insecure')</script>Test</b>",
+ "<b>Test</b>"],
+ ['<div align="left">Test</div>', '<div align="left">Test</div>'],
+ ['<font href="test" size="1">Test</font>',
+ '<font size="1">Test</font>'],
+ ];
+ for (var i = 0; i < examples.length; i++) {
+ var input = examples[i][0], output = examples[i][1];
+ QUnit.strictEqual(Sao.HtmlSanitizer.sanitize(input), output,
+ 'Sao.HtmlSanitizer.sanitize(' + input + ')');
+ }
+ });
+
/*
QUnit.test('CRUD', function() {
var run_tests = function() {