[IMP] tools: sanitizer: add and clean sanitize options
Now having * sanitize: run the sanitizer to clean the html (removing javascripts, unwanted tags, ...) * sanitize_tags: only a subset of tags is allowed in html content. Unwelcomed tags are remove dand their content stripped. * sanitize_attributes: only a subset of attributes is allowed. * sanitize_style: only a subset of style attributes is allowed. Style attributes are parsed to keep only a white list. * strip_style: all style is removed. It bypasses sanitize_style as there is no need to sanitize something that is removed. * strip_classes: remove class attributes Fields parameters have also been updated to match the sanitize options. Html fields by default are sanitized with sanitize_tags activated but without any further options. All addons have been updated to match the new options according to their previous behavior.
This commit is contained in:
@@ -184,7 +184,7 @@ class EventEvent(models.Model):
|
||||
readonly=False, states={'done': [('readonly', True)]})
|
||||
country_id = fields.Many2one('res.country', 'Country', related='address_id.country_id', store=True)
|
||||
description = fields.Html(
|
||||
string='Description', oldname='note', translate=html_translate, sanitize=False,
|
||||
string='Description', oldname='note', translate=html_translate, sanitize_attributes=False,
|
||||
readonly=False, states={'done': [('readonly', True)]})
|
||||
# badge fields
|
||||
badge_front = fields.Html(string='Badge Front')
|
||||
|
||||
@@ -50,7 +50,7 @@ class Message(models.Model):
|
||||
# content
|
||||
subject = fields.Char('Subject')
|
||||
date = fields.Datetime('Date', default=fields.Datetime.now)
|
||||
body = fields.Html('Contents', default='', strip_classes=True)
|
||||
body = fields.Html('Contents', default='', sanitize_style=True, strip_classes=True)
|
||||
attachment_ids = fields.Many2many(
|
||||
'ir.attachment', 'message_attachment_rel',
|
||||
'message_id', 'attachment_id',
|
||||
|
||||
@@ -139,7 +139,7 @@ class MailTemplate(models.Model):
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
|
||||
help="Optional preferred server for outgoing mails. If not set, the highest "
|
||||
"priority one will be used.")
|
||||
body_html = fields.Html('Body', translate=True, sanitize=False)
|
||||
body_html = fields.Html('Body', translate=True, sanitize_attributes=False)
|
||||
report_name = fields.Char('Report Filename', translate=True,
|
||||
help="Name to use for the generated report file (may contain placeholders)\n"
|
||||
"The extension can be omitted and will then come from the report type.")
|
||||
|
||||
@@ -300,7 +300,7 @@ class MassMailing(models.Model):
|
||||
create_date = fields.Datetime(string='Creation Date')
|
||||
sent_date = fields.Datetime(string='Sent Date', oldname='date', copy=False)
|
||||
schedule_date = fields.Datetime(string='Schedule in the Future')
|
||||
body_html = fields.Html(string='Body', translate=html_translate, sanitize=False)
|
||||
body_html = fields.Html(string='Body', translate=html_translate, sanitize_attributes=False)
|
||||
attachment_ids = fields.Many2many('ir.attachment', 'mass_mailing_ir_attachments_rel',
|
||||
'mass_mailing_id', 'attachment_id', string='Attachments')
|
||||
keep_archives = fields.Boolean(string='Keep Archives')
|
||||
|
||||
@@ -110,7 +110,7 @@ class BlogPost(models.Model):
|
||||
default='{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}')
|
||||
blog_id = fields.Many2one('blog.blog', 'Blog', required=True, ondelete='cascade')
|
||||
tag_ids = fields.Many2many('blog.tag', string='Tags')
|
||||
content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize=False)
|
||||
content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize_attributes=False)
|
||||
teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser')
|
||||
teaser_manual = fields.Text(string='Teaser Content')
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class Track(models.Model):
|
||||
state = fields.Selection([
|
||||
('draft', 'Proposal'), ('confirmed', 'Confirmed'), ('announced', 'Announced'), ('published', 'Published'), ('refused', 'Refused'), ('cancel', 'Cancelled')],
|
||||
'Status', default='draft', required=True, copy=False, track_visibility='onchange')
|
||||
description = fields.Html('Track Description', translate=html_translate, sanitize=False)
|
||||
description = fields.Html('Track Description', translate=html_translate, sanitize_attributes=False)
|
||||
date = fields.Datetime('Track Date')
|
||||
duration = fields.Float('Duration', default=1.5)
|
||||
location_id = fields.Many2one('event.track.location', 'Room')
|
||||
|
||||
@@ -711,7 +711,7 @@ class Post(models.Model):
|
||||
question = self.parent_id
|
||||
values = {
|
||||
'author_id': self.sudo().create_uid.partner_id.id, # use sudo here because of access to res.users model
|
||||
'body': tools.html_sanitize(self.content, strict=True, strip_style=True, strip_classes=True),
|
||||
'body': tools.html_sanitize(self.content, sanitize_attributes=True, strip_style=True, strip_classes=True),
|
||||
'message_type': 'comment',
|
||||
'subtype': 'mail.mt_comment',
|
||||
'date': self.create_date,
|
||||
|
||||
@@ -47,7 +47,7 @@ class Job(models.Model):
|
||||
default_description = self.env["ir.model.data"].xmlid_to_object("website_hr_recruitment.default_website_description")
|
||||
return (default_description.render() if default_description else "")
|
||||
|
||||
website_description = fields.Html('Website description', translate=html_translate, sanitize=False, default=_get_default_website_description)
|
||||
website_description = fields.Html('Website description', translate=html_translate, sanitize_attributes=False, default=_get_default_website_description)
|
||||
|
||||
@api.multi
|
||||
def _compute_website_url(self):
|
||||
|
||||
@@ -17,4 +17,4 @@ class ImLivechatChannel(models.Model):
|
||||
for channel in self:
|
||||
channel.website_url = "/livechat/channel/%s" % (slug(channel),)
|
||||
|
||||
website_description = fields.Html("Website description", default=False, help="Description of the channel displayed on the website page", sanitize=False, translate=html_translate)
|
||||
website_description = fields.Html("Website description", default=False, help="Description of the channel displayed on the website page", sanitize_attributes=False, translate=html_translate)
|
||||
|
||||
@@ -18,6 +18,6 @@ class MassMailingList(models.Model):
|
||||
</div>
|
||||
<p class="o_message_paragraph">Join our Marketing newsletter and get <strong>this white paper instantly</strong></p>"""
|
||||
|
||||
popup_content = fields.Html(string="Website Popup Content", translate=True, sanitize=False,
|
||||
popup_content = fields.Html(string="Website Popup Content", translate=True, sanitize_attributes=False,
|
||||
default=_default_popup_content)
|
||||
popup_redirect_url = fields.Char(string="Website Popup Redirect URL", default='/')
|
||||
|
||||
@@ -7,5 +7,5 @@ from odoo import fields, models
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
website_description = fields.Html('Description for the website', sanitize=False) # hack, if website_sale is not installed
|
||||
quote_description = fields.Html('Description for the quote', sanitize=False)
|
||||
website_description = fields.Html('Description for the website', sanitize_attributes=False) # hack, if website_sale is not installed
|
||||
quote_description = fields.Html('Description for the quote', sanitize_attributes=False)
|
||||
|
||||
@@ -56,7 +56,7 @@ class SaleOrder(models.Model):
|
||||
'sale.quote.template', 'Quotation Template',
|
||||
default=_get_default_template_id, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]})
|
||||
website_description = fields.Html('Description', sanitize=False, translate=html_translate)
|
||||
website_description = fields.Html('Description', sanitize_attributes=False, translate=html_translate)
|
||||
options = fields.One2many(
|
||||
'sale.order.option', 'order_id', 'Optional Products Lines',
|
||||
copy=True, readonly=True,
|
||||
@@ -199,7 +199,7 @@ class SaleOrderOption(models.Model):
|
||||
name = fields.Text('Description', required=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)])
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
website_description = fields.Html('Line Description', sanitize=False, translate=html_translate)
|
||||
website_description = fields.Html('Line Description', sanitize_attributes=False, translate=html_translate)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
discount = fields.Float('Discount (%)', digits=dp.get_precision('Discount'))
|
||||
uom_id = fields.Many2one('product.uom', 'Unit of Measure ', required=True)
|
||||
|
||||
@@ -11,7 +11,7 @@ class SaleQuoteTemplate(models.Model):
|
||||
_description = "Sale Quotation Template"
|
||||
|
||||
name = fields.Char('Quotation Template', required=True)
|
||||
website_description = fields.Html('Description', translate=html_translate, sanitize=False)
|
||||
website_description = fields.Html('Description', translate=html_translate, sanitize_attributes=False)
|
||||
quote_line = fields.One2many('sale.quote.line', 'quote_id', 'Quotation Template Lines', copy=True)
|
||||
note = fields.Text('Terms and conditions')
|
||||
options = fields.One2many('sale.quote.option', 'template_id', 'Optional Products Lines', copy=True)
|
||||
@@ -100,7 +100,7 @@ class SaleQuoteOption(models.Model):
|
||||
name = fields.Text('Description', required=True, translate=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True)
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
website_description = fields.Html('Option Description', translate=html_translate, sanitize=False)
|
||||
website_description = fields.Html('Option Description', translate=html_translate, sanitize_attributes=False)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
discount = fields.Float('Discount (%)', digits=dp.get_precision('Discount'))
|
||||
uom_id = fields.Many2one('product.uom', 'Unit of Measure ', required=True)
|
||||
|
||||
@@ -81,7 +81,7 @@ class ProductTemplate(models.Model):
|
||||
domain=lambda self: ['&', ('model', '=', self._name), ('message_type', '=', 'comment')],
|
||||
string='Website Comments',
|
||||
)
|
||||
website_description = fields.Html('Description for the website', sanitize=False, translate=True)
|
||||
website_description = fields.Html('Description for the website', sanitize_attributes=False, translate=True)
|
||||
alternative_product_ids = fields.Many2many('product.template', 'product_alternative_rel', 'src_id', 'dest_id',
|
||||
string='Suggested Products', help='Appear on the product page')
|
||||
accessory_product_ids = fields.Many2many('product.product', 'product_accessory_rel', 'src_id', 'dest_id',
|
||||
|
||||
@@ -34,7 +34,7 @@ class Channel(models.Model):
|
||||
|
||||
name = fields.Char('Name', translate=True, required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
description = fields.Html('Description', translate=html_translate, sanitize=False)
|
||||
description = fields.Html('Description', translate=html_translate, sanitize_attributes=False)
|
||||
sequence = fields.Integer(default=10, help='Display order')
|
||||
category_ids = fields.One2many('slide.category', 'channel_id', string="Categories")
|
||||
slide_ids = fields.One2many('slide.slide', 'channel_id', string="Slides")
|
||||
@@ -102,7 +102,7 @@ class Channel(models.Model):
|
||||
string='Channel Groups', help="Groups allowed to see presentations in this channel")
|
||||
access_error_msg = fields.Html(
|
||||
'Error Message', help="Message to display when not accessible due to access rights",
|
||||
default="<p>This channel is private and its content is restricted to some users.</p>", translate=html_translate, sanitize=False)
|
||||
default="<p>This channel is private and its content is restricted to some users.</p>", translate=html_translate, sanitize_attributes=False)
|
||||
upload_group_ids = fields.Many2many(
|
||||
'res.groups', 'rel_upload_groups', 'channel_id', 'group_id',
|
||||
string='Upload Groups', help="Groups allowed to upload presentations in this channel. If void, every user can upload.")
|
||||
|
||||
@@ -133,18 +133,25 @@ class TestSanitizer(unittest.TestCase):
|
||||
]
|
||||
|
||||
for test, in_lst, out_lst in test_data:
|
||||
new_html = html_sanitize(test, strict=False, strip_style=False, strip_classes=False)
|
||||
new_html = html_sanitize(test, sanitize_attributes=False, sanitize_style=True, strip_style=False, strip_classes=False)
|
||||
for text in in_lst:
|
||||
self.assertIn(text, new_html)
|
||||
for text in out_lst:
|
||||
self.assertNotIn(text, new_html)
|
||||
|
||||
# style should not be sanitized if removed
|
||||
new_html = html_sanitize(test_data[0][0], strict=False, strip_style=True, strip_classes=False)
|
||||
new_html = html_sanitize(test_data[0][0], sanitize_attributes=False, strip_style=True, strip_classes=False)
|
||||
self.assertEqual(new_html, u'<span>Coin coin </span>')
|
||||
|
||||
def test_style_class(self):
|
||||
html = html_sanitize(test_mail_examples.REMOVE_CLASS, strict=True, strip_style=False, strip_classes=True)
|
||||
html = html_sanitize(test_mail_examples.REMOVE_CLASS, sanitize_attributes=True, sanitize_style=True, strip_classes=True)
|
||||
for ext in test_mail_examples.REMOVE_CLASS_IN:
|
||||
self.assertIn(ext, html)
|
||||
for ext in test_mail_examples.REMOVE_CLASS_OUT:
|
||||
self.assertNotIn(ext, html,)
|
||||
|
||||
def test_style_class_only(self):
|
||||
html = html_sanitize(test_mail_examples.REMOVE_CLASS, sanitize_attributes=False, sanitize_style=True, strip_classes=True)
|
||||
for ext in test_mail_examples.REMOVE_CLASS_IN:
|
||||
self.assertIn(ext, html)
|
||||
for ext in test_mail_examples.REMOVE_CLASS_OUT:
|
||||
|
||||
@@ -193,9 +193,9 @@ class MixedModel(models.Model):
|
||||
reference = fields.Reference(string='Related Document',
|
||||
selection='_reference_models')
|
||||
comment1 = fields.Html(sanitize=False)
|
||||
comment2 = fields.Html(sanitize=True, strip_classes=False)
|
||||
comment3 = fields.Html(sanitize=True, strip_classes=True)
|
||||
comment4 = fields.Html(sanitize=True, strip_style=True)
|
||||
comment2 = fields.Html(sanitize_attributes=True, strip_classes=False)
|
||||
comment3 = fields.Html(sanitize_attributes=True, strip_classes=True)
|
||||
comment4 = fields.Html(sanitize_attributes=True, strip_style=True)
|
||||
|
||||
currency_id = fields.Many2one('res.currency', default=lambda self: self.env.ref('base.EUR'))
|
||||
amount = fields.Monetary()
|
||||
|
||||
@@ -605,9 +605,9 @@ class TestHtmlField(common.TransactionCase):
|
||||
|
||||
def test_00_sanitize(self):
|
||||
self.assertEqual(self.model._fields['comment1'].sanitize, False)
|
||||
self.assertEqual(self.model._fields['comment2'].sanitize, True)
|
||||
self.assertEqual(self.model._fields['comment2'].sanitize_attributes, True)
|
||||
self.assertEqual(self.model._fields['comment2'].strip_classes, False)
|
||||
self.assertEqual(self.model._fields['comment3'].sanitize, True)
|
||||
self.assertEqual(self.model._fields['comment3'].sanitize_attributes, True)
|
||||
self.assertEqual(self.model._fields['comment3'].strip_classes, True)
|
||||
|
||||
some_ugly_html = """<p>Oops this should maybe be sanitized
|
||||
|
||||
+23
-2
@@ -1304,7 +1304,10 @@ class Html(_String):
|
||||
type = 'html'
|
||||
_slots = {
|
||||
'sanitize': True, # whether value must be sanitized
|
||||
'strip_style': False, # whether to strip style attributes (otherwise: sanitized)
|
||||
'sanitize_tags': True, # whether to sanitize tags (only a white list of attributes is accepted)
|
||||
'sanitize_attributes': True, # whether to sanitize attributes (only a white list of attributes is accepted)
|
||||
'sanitize_style': False, # whether to sanitize style attributes
|
||||
'strip_style': False, # whether to strip style attributes (removed and therefore not sanitized)
|
||||
'strip_classes': False, # whether to strip classes attributes
|
||||
}
|
||||
|
||||
@@ -1318,6 +1321,18 @@ class Html(_String):
|
||||
_related_sanitize = property(attrgetter('sanitize'))
|
||||
_description_sanitize = property(attrgetter('sanitize'))
|
||||
|
||||
_column_sanitize_tags = property(attrgetter('sanitize_tags'))
|
||||
_related_sanitize_tags = property(attrgetter('sanitize_tags'))
|
||||
_description_sanitize_tags = property(attrgetter('sanitize_tags'))
|
||||
|
||||
_column_sanitize_attributes = property(attrgetter('sanitize_attributes'))
|
||||
_related_sanitize_attributes = property(attrgetter('sanitize_attributes'))
|
||||
_description_sanitize_attributes = property(attrgetter('sanitize_attributes'))
|
||||
|
||||
_column_sanitize_style = property(attrgetter('sanitize_style'))
|
||||
_related_sanitize_style = property(attrgetter('sanitize_style'))
|
||||
_description_sanitize_style = property(attrgetter('sanitize_style'))
|
||||
|
||||
_column_strip_style = property(attrgetter('strip_style'))
|
||||
_related_strip_style = property(attrgetter('strip_style'))
|
||||
_description_strip_style = property(attrgetter('strip_style'))
|
||||
@@ -1330,7 +1345,13 @@ class Html(_String):
|
||||
if value is None or value is False:
|
||||
return False
|
||||
if validate and self.sanitize:
|
||||
return html_sanitize(value, silent=True, strict=True, strip_style=self.strip_style, strip_classes=self.strip_classes)
|
||||
return html_sanitize(
|
||||
value, silent=True,
|
||||
sanitize_tags=self.sanitize_tags,
|
||||
sanitize_attributes=self.sanitize_attributes,
|
||||
sanitize_style=self.sanitize_style,
|
||||
strip_style=self.strip_style,
|
||||
strip_classes=self.strip_classes)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
+14
-3
@@ -339,18 +339,26 @@ class text(_column):
|
||||
class html(text):
|
||||
_type = 'html'
|
||||
_symbol_c = '%s'
|
||||
__slots__ = ['_sanitize', '_strip_style', '_strip_classes', '_symbol_f', '_symbol_set']
|
||||
__slots__ = ['_sanitize', '_sanitize_tags', '_sanitize_attributes', '_sanitize_style', '_strip_style', '_strip_classes', '_symbol_f', '_symbol_set']
|
||||
|
||||
def _symbol_set_html(self, value):
|
||||
if value is None or value is False:
|
||||
return None
|
||||
if not self._sanitize:
|
||||
return value
|
||||
return html_sanitize(value, silent=True, strict=True, strip_style=self._strip_style, strip_classes=self._strip_classes)
|
||||
return html_sanitize(
|
||||
value, silent=True,
|
||||
sanitize_attributes=self._sanitize_attributes,
|
||||
sanitize_style=self._sanitize_style,
|
||||
strip_style=self._strip_style,
|
||||
strip_classes=self._strip_classes)
|
||||
|
||||
def __init__(self, string='unknown', sanitize=True, strip_style=False, strip_classes=False, **args):
|
||||
def __init__(self, string='unknown', sanitize=True, sanitize_tags=True, sanitize_attributes=True, sanitize_style=False, strip_style=False, strip_classes=False, **args):
|
||||
super(html, self).__init__(string=string, **args)
|
||||
self._sanitize = sanitize
|
||||
self._sanitize_tags = sanitize_tags
|
||||
self._sanitize_attributes = sanitize_attributes
|
||||
self._sanitize_style = sanitize_style
|
||||
self._strip_style = strip_style
|
||||
self._strip_classes = strip_classes
|
||||
# symbol_set redefinition because of sanitize specific behavior
|
||||
@@ -360,6 +368,9 @@ class html(text):
|
||||
def to_field_args(self):
|
||||
args = super(html, self).to_field_args()
|
||||
args['sanitize'] = self._sanitize
|
||||
args['sanitize_tags'] = self._sanitize_tags
|
||||
args['sanitize_attributes'] = self._sanitize_attributes
|
||||
args['sanitize_style'] = self._sanitize_style
|
||||
args['strip_style'] = self._strip_style
|
||||
args['strip_classes'] = self._strip_classes
|
||||
return args
|
||||
|
||||
+39
-24
@@ -50,6 +50,9 @@ class _Cleaner(clean.Cleaner):
|
||||
# tables
|
||||
'border-collapse', 'border-spacing', 'caption-side', 'empty-cells', 'table-layout']
|
||||
|
||||
strip_classes = False
|
||||
sanitize_style = False
|
||||
|
||||
def __call__(self, doc):
|
||||
# perform quote detection before cleaning and class removal
|
||||
for el in doc.iter():
|
||||
@@ -57,8 +60,13 @@ class _Cleaner(clean.Cleaner):
|
||||
|
||||
super(_Cleaner, self).__call__(doc)
|
||||
|
||||
# if we keep attributes but still remove classes
|
||||
if not getattr(self, 'safe_attrs_only', False) and self.strip_classes:
|
||||
for el in doc.iter():
|
||||
self.strip_class(el)
|
||||
|
||||
# if we keep style attribute, sanitize them
|
||||
if not self.style:
|
||||
if not self.style and self.sanitize_style:
|
||||
for el in doc.iter():
|
||||
self.parse_style(el)
|
||||
|
||||
@@ -123,6 +131,10 @@ class _Cleaner(clean.Cleaner):
|
||||
if el.getparent() is not None and (el.getparent().get('data-o-mail-quote') or el.getparent().get('data-o-mail-quote-container')) and not el.getparent().get('data-o-mail-quote-node'):
|
||||
el.set('data-o-mail-quote', '1')
|
||||
|
||||
def strip_class(self, el):
|
||||
if el.attrib.get('class'):
|
||||
del el.attrib['class']
|
||||
|
||||
def parse_style(self, el):
|
||||
attributes = el.attrib
|
||||
styling = attributes.get('style')
|
||||
@@ -143,7 +155,7 @@ class _Cleaner(clean.Cleaner):
|
||||
return super(_Cleaner, self).allow_element(el)
|
||||
|
||||
|
||||
def html_sanitize(src, silent=True, strict=False, strip_style=False, strip_classes=False):
|
||||
def html_sanitize(src, silent=True, sanitize_tags=True, sanitize_attributes=False, sanitize_style=False, strip_style=False, strip_classes=False):
|
||||
if not src:
|
||||
return src
|
||||
src = ustr(src, errors='replace')
|
||||
@@ -164,35 +176,38 @@ def html_sanitize(src, silent=True, strict=False, strip_style=False, strip_class
|
||||
|
||||
kwargs = {
|
||||
'page_structure': True,
|
||||
'style': strip_style, # True = remove style tags/attrs
|
||||
'forms': True, # remove form tags
|
||||
'style': strip_style, # True = remove style tags/attrs
|
||||
'sanitize_style': sanitize_style, # True = sanitize styling
|
||||
'forms': True, # True = remove form tags
|
||||
'remove_unknown_tags': False,
|
||||
'allow_tags': allowed_tags,
|
||||
'comments': False,
|
||||
'processing_instructions': False
|
||||
}
|
||||
if etree.LXML_VERSION >= (2, 3, 1):
|
||||
# kill_tags attribute has been added in version 2.3.1
|
||||
if sanitize_tags:
|
||||
kwargs['allow_tags'] = allowed_tags
|
||||
if etree.LXML_VERSION >= (2, 3, 1):
|
||||
# kill_tags attribute has been added in version 2.3.1
|
||||
kwargs.update({
|
||||
'kill_tags': tags_to_kill,
|
||||
'remove_tags': tags_to_remove,
|
||||
})
|
||||
else:
|
||||
kwargs['remove_tags'] = tags_to_kill + tags_to_remove
|
||||
|
||||
if sanitize_attributes and etree.LXML_VERSION >= (3, 1, 0): # lxml < 3.1.0 does not allow to specify safe_attrs. We keep all attributes in order to keep "style"
|
||||
if strip_classes:
|
||||
current_safe_attrs = safe_attrs - frozenset(['class'])
|
||||
else:
|
||||
current_safe_attrs = safe_attrs
|
||||
kwargs.update({
|
||||
'kill_tags': tags_to_kill,
|
||||
'remove_tags': tags_to_remove,
|
||||
'safe_attrs_only': True,
|
||||
'safe_attrs': current_safe_attrs,
|
||||
})
|
||||
else:
|
||||
kwargs['remove_tags'] = tags_to_kill + tags_to_remove
|
||||
|
||||
if strict:
|
||||
if etree.LXML_VERSION >= (3, 1, 0):
|
||||
# lxml < 3.1.0 does not allow to specify safe_attrs. We keep all attributes in order to keep "style"
|
||||
if strip_classes:
|
||||
current_safe_attrs = safe_attrs - frozenset(['class'])
|
||||
else:
|
||||
current_safe_attrs = safe_attrs
|
||||
kwargs.update({
|
||||
'safe_attrs_only': True,
|
||||
'safe_attrs': current_safe_attrs,
|
||||
})
|
||||
else:
|
||||
kwargs['safe_attrs_only'] = False # keep oe-data attributes + style
|
||||
kwargs.update({
|
||||
'safe_attrs_only': False, # keep oe-data attributes + style
|
||||
'strip_classes': strip_classes, # remove classes, even when keeping other attributes
|
||||
})
|
||||
|
||||
try:
|
||||
# some corner cases make the parser crash (such as <SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT> in test_mail)
|
||||
|
||||
Reference in New Issue
Block a user