From 8c77c711eeaa2aea34094574e1046e43a049cb9e Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Tue, 18 Aug 2015 08:42:41 +0200 Subject: [PATCH] [FIX] website: escaping saved html content Escape text nodes changed via the web editor before sending the content it to the server controller. It is done since the content is unescaped one time when being displayed, and it is not done for inline style and script tags (which may be injected by dropping a snippet) since that would break them. replacing the solution in cdb900044. --- addons/website/models/ir_ui_view.py | 9 ------ .../website/static/src/js/website.editor.js | 9 +++++- addons/website/tests/test_views.py | 28 ++++++++++++++++--- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 2895379dcf4..9a3a84a9adb 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -7,7 +7,6 @@ from openerp import SUPERUSER_ID, api from openerp.addons.website.models import website from openerp.http import request from openerp.osv import osv, fields -from openerp.tools import html_escape class view(osv.osv): _inherit = "ir.ui.view" @@ -120,14 +119,6 @@ class view(osv.osv): # ensure there's only one match [root] = arch.xpath(section_xpath) - # html text need to be escaped for xml storage - def escape_node(node): - node.text = node.text and html_escape(node.text) - node.tail = node.tail and html_escape(node.tail) - escape_node(replacement) - for descendant in replacement.iterdescendants(): - escape_node(descendant) - root.text = replacement.text root.tail = replacement.tail # replace all children diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 4a3e8ee4f19..43df01d3514 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -423,7 +423,14 @@ * Saves an RTE content, which always corresponds to a view section (?). */ saveElement: function ($el) { - var markup = $el.prop('outerHTML'); + // escape text nodes for xml saving + var escaped_el = $el.clone(); + escaped_el.find('*').addBack().not('script,style').contents().each(function(){ + if(this.nodeType == 3) { + this.nodeValue = _.escape(this.nodeValue); + } + }); + var markup = escaped_el.prop('outerHTML'); return openerp.jsonRpc('/web/dataset/call', 'call', { model: 'ir.ui.view', method: 'save', diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py index c8e7efb9044..b63ac75997d 100644 --- a/addons/website/tests/test_views.py +++ b/addons/website/tests/test_views.py @@ -175,14 +175,34 @@ class TestViewSaving(common.TransactionCase): ) def test_save_escaped_text(self): + """ Test saving html special chars in text nodes """ view_id = self.registry('ir.ui.view').create(self.cr, self.uid, { - 'arch':'hello world', + 'arch':'

hello world

', 'type':'qweb' }) view = self.registry('ir.ui.view').browse(self.cr, self.uid, view_id) - replacement = 'hello world & <angle brackets>!' - view.save(replacement, xpath='/t') - self.assertEqual(view.render(), replacement, 'html special characters wrongly escaped') + # script and style text nodes should not escaped client side + replacement = '' + view.save(replacement, xpath='/t/p/h1') + self.assertIn( + replacement.replace('&', '&'), + view.arch, + 'inline script should be escaped server side' + ) + self.assertIn( + replacement, + view.render(), + 'inline script should not be escaped when rendering' + ) + # common text nodes should be be escaped client side + replacement = 'world &amp; &lt;b&gt;cie' + view.save(replacement, xpath='/t/p') + self.assertIn(replacement, view.arch, 'common text node should not be escaped server side') + self.assertIn( + replacement, + view.render().replace('&', '&'), + 'text node characters wrongly unescaped when rendering' + ) def test_save_only_embedded(self): Company = self.registry('res.company')