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 & <b>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')