diff options
| author | Thibault Delavallée <tde@openerp.com> | 2016-08-19 13:49:48 +0200 |
|---|---|---|
| committer | Thibault Delavallée <tde@openerp.com> | 2016-08-30 15:54:56 +0200 |
| commit | 02c748be59ac8abc44c945218587e7b8a8e06a28 (patch) | |
| tree | 1e68f807714a08b850c66b8d9c9b02e47fdc3a5b | |
| parent | 97425bfe3f2b317595e7086aedf7b5689dc9d218 (diff) | |
[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.
21 files changed, 111 insertions, 57 deletions
diff --git a/addons/event/models/event.py b/addons/event/models/event.py index d569a08984b..a0a7e72ae71 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -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') diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 59f707bfc9f..5aa52d755c8 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -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', diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index 8508fa9f250..801cf27f50e 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -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.") diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 9bf1a913b20..de197a48b89 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -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') diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py index d1a4ad93c56..35d611f162e 100644 --- a/addons/website_blog/models/website_blog.py +++ b/addons/website_blog/models/website_blog.py @@ -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') diff --git a/addons/website_event_track/models/website_event_track.py b/addons/website_event_track/models/website_event_track.py index 7f98f372e45..969bef0fa61 100644 --- a/addons/website_event_track/models/website_event_track.py +++ b/addons/website_event_track/models/website_event_track.py @@ -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') diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index a1bdeb9fbde..3ef6636af73 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -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, diff --git a/addons/website_hr_recruitment/models/hr_recruitment.py b/addons/website_hr_recruitment/models/hr_recruitment.py index 4d77e37e459..1a0960a444e 100644 --- a/addons/website_hr_recruitment/models/hr_recruitment.py +++ b/addons/website_hr_recruitment/models/hr_recruitment.py @@ -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): diff --git a/addons/website_livechat/models/im_livechat.py b/addons/website_livechat/models/im_livechat.py index f73d3b594f7..17ad3f89a12 100644 --- a/addons/website_livechat/models/im_livechat.py +++ b/addons/website_livechat/models/im_livechat.py @@ -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) diff --git a/addons/website_mass_mailing/models/mass_mailing_list.py b/addons/website_mass_mailing/models/mass_mailing_list.py index 7058e385b05..3e49bcba59b 100644 --- a/addons/website_mass_mailing/models/mass_mailing_list.py +++ b/addons/website_mass_mailing/models/mass_mailing_list.py @@ -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='/') diff --git a/addons/website_quote/models/product_template.py b/addons/website_quote/models/product_template.py index ef2773225f1..55f3fbc1dec 100644 --- a/addons/website_quote/models/product_template.py +++ b/addons/website_quote/models/product_template.py @@ -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) diff --git a/addons/website_quote/models/sale_order.py b/addons/website_quote/models/sale_order.py index 513185d0075..4e069e2c916 100644 --- a/addons/website_quote/models/sale_order.py +++ b/addons/website_quote/models/sale_order.py @@ -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) diff --git a/addons/website_quote/models/sale_quote.py b/addons/website_quote/models/sale_quote.py index 895c2930d89..817df1e087c 100644 --- a/addons/website_quote/models/sale_quote.py +++ b/addons/website_quote/models/sale_quote.py @@ -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) diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index ef1096f6285..fe2bb8dcf64 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -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', diff --git a/addons/website_slides/models/slides.py b/addons/website_slides/models/slides.py index b2e6fb0ae7f..602b95014e7 100644 --- a/addons/website_slides/models/slides.py +++ b/addons/website_slides/models/slides.py @@ -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.") diff --git a/openerp/addons/base/tests/test_mail.py b/openerp/addons/base/tests/test_mail.py index a751b7a76e3..795d6824886 100644 --- a/openerp/addons/base/tests/test_mail.py +++ b/openerp/addons/base/tests/test_mail.py @@ -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: diff --git a/openerp/addons/test_new_api/models.py b/openerp/addons/test_new_api/models.py index bd0c9b250cb..e302c25e031 100644 --- a/openerp/addons/test_new_api/models.py +++ b/openerp/addons/test_new_api/models.py @@ -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() diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py index 7cc018744f8..068d300abb1 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -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 diff --git a/openerp/fields.py b/openerp/fields.py index 22b54e69be8..e25dbfdb884 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -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 diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 413bea7bea0..8275efa4b4a 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -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) - - def __init__(self, string='unknown', sanitize=True, strip_style=False, strip_classes=False, **args): + 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, 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 diff --git a/openerp/tools/mail.py b/openerp/tools/mail.py index 3714df9c56f..2e432e92941 100644 --- a/openerp/tools/mail.py +++ b/openerp/tools/mail.py @@ -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 - kwargs.update({ - 'kill_tags': tags_to_kill, - 'remove_tags': tags_to_remove, - }) - 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 + 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({ - 'safe_attrs_only': True, - 'safe_attrs': current_safe_attrs, + '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({ + '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) |
