[FIX] website,website_sale: HTML escape dynamic snippet

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) <jke@openerp.com>
This commit is contained in:
Laurent Stukkens (LTU)
2020-10-12 17:45:45 +00:00
parent 26f4fc093c
commit 0ef25d19bc
3 changed files with 19 additions and 14 deletions
+2 -3
View File
@@ -707,6 +707,8 @@
<field name="url">/website/static/src/img/snippets_demo/s_popup.jpg</field>
</record>
</data>
<data>
<!-- Filter for Dynamic Snippet -->
<record id="dynamic_snippet_country_filter" model="ir.filters">
<field name="name">Countries</field>
@@ -769,9 +771,6 @@
<field name="name">Countries</field>
<field name="website_id" ref="website.default_website"/>
</record>
</data>
<data>
<record id="group_multi_website" model="res.groups">
<field name="name">Multi-website</field>
<field name="category_id" ref="base.module_category_hidden"/>
@@ -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)
+8 -7
View File
@@ -46,6 +46,8 @@
<field name="website_id" eval="False"/>
</record>
</data>
<data>
<!-- Action Server for Dynamic Filter -->
<record id="dynamic_snippet_products_action" model="ir.actions.server">
<field name="name">Products Dynamic Snippet</field>
@@ -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
<field name="name">Products</field>
<field name="website_id" ref="website.default_website"/>
</record>
</data>
<data>
<function model="ir.model.fields" name="formbuilder_whitelist">
<value>sale.order</value>
<value eval="[