From f1942718231154627e2f891ee55a9e8879115ac0 Mon Sep 17 00:00:00 2001 From: Christophe Monniez Date: Mon, 23 Oct 2023 10:17:36 +0200 Subject: [PATCH] [FIX] http: comply with rfc6265 Werkzeug changed the behavior of url_quote in pallets/werkzeug@babfc93b3834bcbb22163442a9af70141bcc5a81 Which appeared in Werkzeug 2.2.2 used in Debian Bookworm. This change broke at least the export feature in Odoo. In summary, the character set specified by [RFC5987] is more restricted than that of [RFC3986]. So url_quote now allows invalid characters. For example, a filename like `Journal Entry (account.move).xlsx` leads to a crash of the client with Werkzeug 2.2.2. url_quote is not really made to conform to [RFC6266] but we did not find any [RFC6266] escaping tool in the standard library. This commit explicitly specify as unsafe this list of chars. [RFC6266]: https://datatracker.ietf.org/doc/html/rfc6266/ [RFC5987]: https://datatracker.ietf.org/doc/html/rfc5987#section-3.2 [RFC3986]: https://datatracker.ietf.org/doc/html/rfc3986/ [RFC2616]: https://datatracker.ietf.org/doc/html/rfc2616#section-2 closes odoo/odoo#139483 X-original-commit: d145cb57eaf39fbe7612d4a79f209fc191828a8b Signed-off-by: Julien Castiaux (juc) Signed-off-by: Christophe Monniez (moc) --- odoo/addons/test_http/tests/test_misc.py | 30 ++++++++++++++++++++++-- odoo/http.py | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/odoo/addons/test_http/tests/test_misc.py b/odoo/addons/test_http/tests/test_misc.py index 55f470df737a7..6e28b86da2f15 100644 --- a/odoo/addons/test_http/tests/test_misc.py +++ b/odoo/addons/test_http/tests/test_misc.py @@ -7,9 +7,9 @@ from urllib.parse import urlparse import odoo -from odoo.http import root +from odoo.http import root, content_disposition from odoo.tests import tagged -from odoo.tests.common import HOST, new_test_user, get_db_name +from odoo.tests.common import HOST, new_test_user, get_db_name, BaseCase from odoo.tools import config, file_path from odoo.addons.test_http.controllers import CT_JSON @@ -237,3 +237,29 @@ def test_ensure_db3_change_db(self): res.raise_for_status() self.assertEqual(res.status_code, 200) self.assertEqual(res.text, 'db1') + +class TestContentDisposition(BaseCase): + + def test_content_disposition(self): + """ Test that content_disposition filename conforms to RFC 6266, RFC 5987 """ + assertions = [ + ('foo bar.xls', 'foo%20bar.xls', 'Space character'), + ('foo(bar).xls', 'foo%28bar%29.xls', 'Parenthesis'), + ('foo.xls', 'foo%3Cbar%3E.xls', 'Angle brackets'), + ('foo[bar].xls', 'foo%5Bbar%5D.xls', 'Brackets'), + ('foo{bar}.xls', 'foo%7Bbar%7D.xls', 'Curly brackets'), + ('foo@bar.xls', 'foo%40bar.xls', 'At sign'), + ('foo,bar.xls', 'foo%2Cbar.xls', 'Comma sign'), + ('foo;bar.xls', 'foo%3Bbar.xls', 'Semicolon sign'), + ('foo:bar.xls', 'foo%3Abar.xls', 'Colon sign'), + ('foo\\bar.xls', 'foo%5Cbar.xls', 'Backslash sign'), + ('foo"bar.xls', 'foo%22bar.xls', 'Double quote sign'), + ('foo/bar.xls', 'foo%2Fbar.xls', 'Slash sign'), + ('foo?bar.xls', 'foo%3Fbar.xls', 'Question mark'), + ('foo=bar.xls', 'foo%3Dbar.xls', 'Equal sign'), + ('foo*bar.xls', 'foo%2Abar.xls', 'Star sign'), + ("foo'bar.xls", 'foo%27bar.xls', 'Single-quote sign'), + ('foo%bar.xls', 'foo%25bar.xls', 'Percent sign'), + ] + for filename, pct_encoded, hint in assertions: + self.assertEqual(content_disposition(filename), f"attachment; filename*=UTF-8''{pct_encoded}", f'{hint} should be percent encoded') diff --git a/odoo/http.py b/odoo/http.py index 0156a8558c3da..366528e120b9f 100644 --- a/odoo/http.py +++ b/odoo/http.py @@ -305,7 +305,7 @@ class SessionExpiredException(Exception): def content_disposition(filename): return "attachment; filename*=UTF-8''{}".format( - url_quote(filename, safe='') + url_quote(filename, safe='', unsafe='()<>@,;:"/[]?={}\\*\'%') # RFC6266 ) def db_list(force=False, host=None):