From 0ef25d19bc8cb0523ca21ee054ba86d25e7348cd Mon Sep 17 00:00:00 2001 From: "Laurent Stukkens (LTU)" Date: Fri, 9 Oct 2020 19:38:22 +0000 Subject: [PATCH] [FIX] website,website_sale: HTML escape dynamic snippet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue To do on Community: - Install website_sale (eCommerce) - Go into Website app - Navigate through Product/Products - Edit 'Customizable Desk (CONFIG)' and add '&' and/or '<' and/or '>' characters into the name - Save - Go to the Website app Dashboard and click on 'Go to Website' - Click on 'Edit' button - Drag and drop the 'Dynamic Product' snippet into the website - Click on the block 'Your Dynamic Snippet wil be ...' - In the right panel, in the 'Dynamic Product' snippet options, chose a 'Template' and a 'Product Category' A traceback is shown Cause the '&' character crashes lxml.etree.fromstring Solution ensure no '&' is sent to lxmx.etree.fromstring by using odoo.tools.html_escape (in order to ensure no other problematic characters are sent to the front) on the field values except when not applicable (widget rendering with record_to_html should not be escaped). It is important to note here that this is applicable to action servers that are used by the dynamic filter. opw-2357027 closes odoo/odoo#59805 X-original-commit: f91422115c09be484f2868347566b6392ed0effd Signed-off-by: Jérémy Kersten (jke) --- addons/website/data/website_data.xml | 5 ++--- addons/website/models/website_snippet_filter.py | 13 +++++++++---- addons/website_sale/data/data.xml | 15 ++++++++------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index dda2fe05c90..dc477e723f6 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -707,6 +707,8 @@ /website/static/src/img/snippets_demo/s_popup.jpg + + Countries @@ -769,9 +771,6 @@ Countries - - - Multi-website diff --git a/addons/website/models/website_snippet_filter.py b/addons/website/models/website_snippet_filter.py index 6bdd648f1fd..6613a80ff45 100644 --- a/addons/website/models/website_snippet_filter.py +++ b/addons/website/models/website_snippet_filter.py @@ -5,6 +5,7 @@ from collections import OrderedDict from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from odoo.osv import expression +from odoo.tools import html_escape as escape from lxml import etree as ET @@ -21,6 +22,10 @@ class WebsiteSnippetFilter(models.Model): limit = fields.Integer(help='The limit is the maximum number of records retrieved', required=True) website_id = fields.Many2one('website', string='Website', ondelete='cascade', required=True) + @api.model + def escape_falsy_as_empty(self, s): + return escape(s) if s else '' + @api.constrains('action_server_id', 'filter_id') def _check_data_source_is_provided(self): for record in self: @@ -115,9 +120,9 @@ class WebsiteSnippetFilter(models.Model): field = model._fields.get(field_name) field_widget = field_widget or field.type if field.type == 'binary': - data['image_fields'][field_name] = Website.image_url(record, field_name) + data['image_fields'][field_name] = self.escape_falsy_as_empty(Website.image_url(record, field_name)) elif field_widget == 'image': - data['image_fields'][field_name] = record[field_name] + data['image_fields'][field_name] = self.escape_falsy_as_empty(record[field_name]) elif field_widget == 'monetary': FieldMonetary = self.env['ir.qweb.field.monetary'] model_currency = None @@ -137,11 +142,11 @@ class WebsiteSnippetFilter(models.Model): {'display_currency': website_currency} ) else: - data['fields'][field_name] = record[field_name] + data['fields'][field_name] = self.escape_falsy_as_empty(record[field_name]) elif ('ir.qweb.field.%s' % field_widget) in self.env: data['fields'][field_name] = self.env[('ir.qweb.field.%s' % field_widget)].record_to_html(record, field_name, {}) else: - data['fields'][field_name] = record[field_name] + data['fields'][field_name] = self.escape_falsy_as_empty(record[field_name]) data['fields']['call_to_action_url'] = 'website_url' in record and record['website_url'] values.append(data) diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml index fdef0bf6ae0..c3e23f32161 100644 --- a/addons/website_sale/data/data.xml +++ b/addons/website_sale/data/data.xml @@ -46,6 +46,8 @@ + + Products Dynamic Snippet @@ -60,6 +62,7 @@ dynamic_filter = model.env.context.get('dynamic_filter') limit = model.env.context.get('limit') search_domain = model.env.context.get('search_domain') get_rendering_data_structure = model.env.context.get('get_rendering_data_structure') +escape = dynamic_filter.escape_falsy_as_empty domain = [('website_published', '=', True)] + website.website_domain() + (search_domain or []) products = ProductProduct.search(domain, limit=limit) @@ -82,12 +85,12 @@ for product in products: for field_name in dynamic_filter.field_names.split(","): field = ProductProduct._fields.get(field_name) if field and field.type == 'binary': - res_product[field_name] = website.image_url(product, field_name) - data_type = 'image_fields' + data['image_fields'][field_name] = escape(website.image_url(product, field_name)) + elif field_name == 'list_price': + data['fields'][field_name] = res_product[field_name] else: - data_type = 'fields' - data[data_type][field_name] = res_product[field_name] - data['fields']['call_to_action_url'] = product['website_url'] + data['fields'][field_name] = escape(res_product[field_name]) + data['fields']['call_to_action_url'] = escape(product['website_url']) res_products.append(data) response = res_products @@ -101,8 +104,6 @@ response = res_products Products - - sale.order