From e2f1af78c465916551f12419f37d4e63a6cfd7f2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Fri, 12 May 2017 17:14:52 +0200 Subject: [PATCH] [FIX] P3: remove cgi.escape uses cgi.escape is unsafe (quote=False by default) and deprecated in Python 3. We already have an openerp.tools.misc.html_escape version which forwards to the (modern and safe) werkzeug.utils.escape, just use that everywhere, and convert extant uses of werkzeug.utils.escape to utils.html_escape as well so that we do the same thing everywhere. --- addons/base_import/controllers/main.py | 4 ++-- .../google_spreadsheet/models/google_drive.py | 4 ++-- addons/hw_posbox_homepage/controllers/main.py | 3 ++- .../models/pos_mercury_transaction.py | 7 ++----- odoo/addons/base/ir/ir_qweb/assetsbundle.py | 3 +-- odoo/addons/base/tests/test_mail.py | 21 +++++++++---------- odoo/addons/base/tests/test_qweb.py | 8 +++---- odoo/tools/mail.py | 13 ++++++------ 8 files changed, 28 insertions(+), 35 deletions(-) diff --git a/addons/base_import/controllers/main.py b/addons/base_import/controllers/main.py index 23febc086e0..7eff0aa420d 100644 --- a/addons/base_import/controllers/main.py +++ b/addons/base_import/controllers/main.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import cgi import json from odoo import http from odoo.http import request +from odoo.tools import misc class ImportController(http.Controller): @@ -20,4 +20,4 @@ class ImportController(http.Controller): 'file_type': file.content_type, }) - return 'window.top.%s(%s)' % (cgi.escape(jsonp), json.dumps({'result': written})) + return 'window.top.%s(%s)' % (misc.html_escape(jsonp), json.dumps({'result': written})) diff --git a/addons/google_spreadsheet/models/google_drive.py b/addons/google_spreadsheet/models/google_drive.py index dce4777a25e..08c0774cc83 100644 --- a/addons/google_spreadsheet/models/google_drive.py +++ b/addons/google_spreadsheet/models/google_drive.py @@ -1,6 +1,5 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -import cgi import json import logging @@ -10,6 +9,7 @@ import re import werkzeug.urls from odoo import api, models +from odoo.tools import misc from odoo.addons.google_account import TIMEOUT _logger = logging.getLogger(__name__) @@ -70,7 +70,7 @@ class GoogleDrive(models.Model): href="https://spreadsheets.google.com/feeds/cells/{key}/od6/private/full/R60C15"/> -''' .format(key=spreadsheet_key, formula=cgi.escape(formula, quote=True), config=cgi.escape(config_formula, quote=True)) +''' .format(key=spreadsheet_key, formula=misc.html_escape(formula), config=misc.html_escape(config_formula)) try: req = requests.post( diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py index f43e585fcba..bdcafdae706 100644 --- a/addons/hw_posbox_homepage/controllers/main.py +++ b/addons/hw_posbox_homepage/controllers/main.py @@ -8,6 +8,7 @@ import werkzeug import odoo from odoo import http +from odoo.tools import misc _logger = logging.getLogger(__name__) @@ -106,7 +107,7 @@ class PosboxHomepage(odoo.addons.web.controllers.main.Home): f = open('/tmp/scanned_networks.txt', 'r') for line in f: line = line.rstrip() - line = werkzeug.utils.escape(line) + line = misc.html_escape(line) wifi_template += '\n' f.close() except IOError: diff --git a/addons/pos_mercury/models/pos_mercury_transaction.py b/addons/pos_mercury/models/pos_mercury_transaction.py index 5efbdaa0ff5..bd190e82f0d 100644 --- a/addons/pos_mercury/models/pos_mercury_transaction.py +++ b/addons/pos_mercury/models/pos_mercury_transaction.py @@ -3,15 +3,12 @@ from datetime import date, timedelta -import cgi -import ssl - import requests import werkzeug from odoo import models, api, service from odoo.exceptions import UserError -from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, misc class MercuryTransaction(models.Model): @@ -53,7 +50,7 @@ class MercuryTransaction(models.Model): soap_header = '' soap_footer = '' + data['merchant_pwd'] + '' - xml_transaction = soap_header + cgi.escape(xml_transaction) + soap_footer + xml_transaction = soap_header + misc.escape_html(xml_transaction) + soap_footer response = '' diff --git a/odoo/addons/base/ir/ir_qweb/assetsbundle.py b/odoo/addons/base/ir/ir_qweb/assetsbundle.py index 6a8ad84c9dd..415be062a10 100644 --- a/odoo/addons/base/ir/ir_qweb/assetsbundle.py +++ b/odoo/addons/base/ir/ir_qweb/assetsbundle.py @@ -12,7 +12,6 @@ from odoo import fields, tools from odoo.http import request from odoo.modules.module import get_resource_path import psycopg2 -import werkzeug from odoo.tools import func, misc, pycompat import logging @@ -587,7 +586,7 @@ class StylesheetAsset(WebAsset): return self.with_header(content) def to_html(self): - media = (' media="%s"' % werkzeug.utils.escape(self.media)) if self.media else '' + media = (' media="%s"' % misc.html_escape(self.media)) if self.media else '' if self.url: href = self.html_url return '' % (href, media) diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py index 795d6824886..1a30ef4690b 100644 --- a/odoo/addons/base/tests/test_mail.py +++ b/odoo/addons/base/tests/test_mail.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import cgi import unittest -from odoo.tools import html_sanitize, append_content_to_html, plaintext2html, email_split +from odoo.tools import html_sanitize, append_content_to_html, plaintext2html, email_split, misc from . import test_mail_examples @@ -99,7 +98,7 @@ class TestSanitizer(unittest.TestCase): "Div nico " ] for email in emails: - self.assertIn(cgi.escape(email), html_sanitize(email), 'html_sanitize stripped emails of original html') + self.assertIn(misc.html_escape(email), html_sanitize(email), 'html_sanitize stripped emails of original html') def test_sanitize_unescape_emails(self): not_emails = [ @@ -108,7 +107,7 @@ class TestSanitizer(unittest.TestCase): for email in not_emails: sanitized = html_sanitize(email) left_part = email.split('>')[0] # take only left part, as the sanitizer could add data information on node - self.assertNotIn(cgi.escape(email), sanitized, 'html_sanitize stripped emails of original html') + self.assertNotIn(misc.html_escape(email), sanitized, 'html_sanitize stripped emails of original html') self.assertIn(left_part, sanitized) def test_style_parsing(self): @@ -172,14 +171,14 @@ class TestSanitizer(unittest.TestCase): for ext in test_mail_examples.QUOTE_BLOCKQUOTE_IN: self.assertIn(ext, html) for ext in test_mail_examples.QUOTE_BLOCKQUOTE_OUT: - self.assertIn('%s' % cgi.escape(ext.decode('utf-8')), html) + self.assertIn('%s' % misc.html_escape(ext.decode('utf-8')), html) def test_quote_thunderbird(self): html = html_sanitize(test_mail_examples.QUOTE_THUNDERBIRD_1) for ext in test_mail_examples.QUOTE_THUNDERBIRD_1_IN: self.assertIn(ext, html) for ext in test_mail_examples.QUOTE_THUNDERBIRD_1_OUT: - self.assertIn('%s' % cgi.escape(ext.decode('utf-8')), html) + self.assertIn('%s' % misc.html_escape(ext.decode('utf-8')), html) def test_quote_hotmail_html(self): html = html_sanitize(test_mail_examples.QUOTE_HOTMAIL_HTML) @@ -226,7 +225,7 @@ class TestSanitizer(unittest.TestCase): for text in in_lst: self.assertIn(text, new_html) for text in out_lst: - self.assertIn('%s' % cgi.escape(text), new_html) + self.assertIn('%s' % misc.html_escape(text), new_html) def test_quote_signature(self): test_data = [ @@ -245,27 +244,27 @@ class TestSanitizer(unittest.TestCase): for ext in test_mail_examples.GMAIL_1_IN: self.assertIn(ext, html) for ext in test_mail_examples.GMAIL_1_OUT: - self.assertIn('%s' % cgi.escape(ext), html) + self.assertIn('%s' % misc.html_escape(ext), html) def test_quote_text(self): html = html_sanitize(test_mail_examples.TEXT_1) for ext in test_mail_examples.TEXT_1_IN: self.assertIn(ext, html) for ext in test_mail_examples.TEXT_1_OUT: - self.assertIn('%s' % cgi.escape(ext), html) + self.assertIn('%s' % misc.html_escape(ext), html) html = html_sanitize(test_mail_examples.TEXT_2) for ext in test_mail_examples.TEXT_2_IN: self.assertIn(ext, html) for ext in test_mail_examples.TEXT_2_OUT: - self.assertIn('%s' % cgi.escape(ext), html) + self.assertIn('%s' % misc.html_escape(ext), html) def test_quote_bugs(self): html = html_sanitize(test_mail_examples.BUG1) for ext in test_mail_examples.BUG_1_IN: self.assertIn(ext, html) for ext in test_mail_examples.BUG_1_OUT: - self.assertIn('%s' % cgi.escape(ext.decode('utf-8')), html) + self.assertIn('%s' % misc.html_escape(ext.decode('utf-8')), html) def test_misc(self): # False / void should not crash diff --git a/odoo/addons/base/tests/test_qweb.py b/odoo/addons/base/tests/test_qweb.py index cd459fe7669..c331f426748 100644 --- a/odoo/addons/base/tests/test_qweb.py +++ b/odoo/addons/base/tests/test_qweb.py @@ -1,19 +1,17 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import cgi import collections import json import os.path import re from lxml import etree -from itertools import chain from odoo.modules import get_module_resource from odoo.tests.common import TransactionCase from odoo.addons.base.ir.ir_qweb import QWebException -from odoo.tools import pycompat +from odoo.tools import pycompat, misc, ustr def dedent_and_strip(string): @@ -48,12 +46,12 @@ class TestQWebTField(TransactionCase): result = self.engine.render(field, {'company': company}) self.assertEqual( - result, + ustr(result), '%s' % ( company.id, - cgi.escape(s.encode('utf-8')), + misc.html_escape(s), ), ) diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 9689512df00..f3396f9137b 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -import cgi import logging import lxml.html.clean as clean import random @@ -16,7 +15,7 @@ from lxml import etree import odoo from odoo.loglevels import ustr -from odoo.tools import pycompat +from odoo.tools import pycompat, misc _logger = logging.getLogger(__name__) @@ -178,10 +177,10 @@ def html_sanitize(src, silent=True, sanitize_tags=True, sanitize_attributes=Fals part = re.compile(r"(<(([^a<>]|a[^<>\s])[^<>]*)@[^<>]+>)", re.IGNORECASE | re.DOTALL) # remove results containing cite="mid:email_like@address" (ex: blockquote cite) # cite_except = re.compile(r"^((?!cite[\s]*=['\"]).)*$", re.IGNORECASE) - src = part.sub(lambda m: ('cite=' not in m.group(1) and 'alt=' not in m.group(1)) and cgi.escape(m.group(1)) or m.group(1), src) + src = part.sub(lambda m: ('cite=' not in m.group(1) and 'alt=' not in m.group(1)) and misc.html_escape(m.group(1)) or m.group(1), src) # html encode mako tags <% ... %> to decode them later and keep them alive, otherwise they are stripped by the cleaner - src = src.replace('<%', cgi.escape('<%')) - src = src.replace('%>', cgi.escape('%>')) + src = src.replace('<%', misc.html_escape('<%')) + src = src.replace('%>', misc.html_escape('%>')) kwargs = { 'page_structure': True, @@ -334,7 +333,7 @@ def html2plaintext(html, body_id=None, encoding='utf-8'): def plaintext2html(text, container_tag=False): """ Convert plaintext into html. Content of the text is escaped to manage - html entities, using cgi.escape(). + html entities, using misc.html_escape(). - all \n,\r are replaced by
- enclose content into

- convert url into clickable link @@ -343,7 +342,7 @@ def plaintext2html(text, container_tag=False): :param string container_tag: container of the html; by default the content is embedded into a

""" - text = cgi.escape(ustr(text)) + text = misc.html_escape(ustr(text)) # 1. replace \n and \r text = text.replace('\n', '
')