diff --git a/CHANGELOG b/CHANGELOG index f943e041..136c172b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Sanitize RichtText fields content (issue9405) * Escape external string (issue9394) * Keep context in sessionStorage * Use existing context for get_preferences diff --git a/COPYRIGHT b/COPYRIGHT index adcd731d..5e599710 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -2,6 +2,7 @@ Copyright (C) 2012-2020 Nicolas Évrard. 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 --git a/Gruntfile.js b/Gruntfile.js index 087920a4..990c6c59 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -23,7 +23,8 @@ module.exports = function(grunt) { 'src/wizard.js', 'src/board.js', 'src/bus.js', - 'src/plugins.js' + 'src/plugins.js', + 'src/html_sanitizer.js' ]; // Project configuration. diff --git a/src/html_sanitizer.js b/src/html_sanitizer.js new file mode 100644 index 00000000..7e38cd19 --- /dev/null +++ b/src/html_sanitizer.js @@ -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 == "
") 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(""); + } + 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(/]*>(\S)/g, "
\n$1") + .replace(/div>
\n').html(content || ''); + var el = jQuery('
').html( + Sao.HtmlSanitizer.sanitize(content || '')); this._normalize(el); return el.html(); }, diff --git a/tests/sao.js b/tests/sao.js index 9cc8a754..7a123247 100644 --- a/tests/sao.js +++ b/tests/sao.js @@ -2800,6 +2800,25 @@ ["Active: False", "Active: True"])); }); + QUnit.test('HTML Sanitization', function() { + var examples = [ + ["Test", "Test"], + ["Test", "Test"], + ["
Test
", "
Test
"], + ["", ""], + ["Test", + "Test"], + ['
Test
', '
Test
'], + ['Test', + 'Test'], + ]; + 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() {