From 7b65a63c97d185d9b6e6f1b0bfbc7968d1e4de49 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Tue, 29 Oct 2019 14:06:18 +0000 Subject: [PATCH] [IMP] tools: restore P2 formataddr default behavior Python 2 `email.tools.formataddr` doesn't encode not RFC-2822 compliant realname to base64 or quoted-printable. This is the wanted behavior to output formatted email addresses on screen. Python 3 `email.tools.formataddr` does encode the realname to be RFC-2822 compliant. This is the wanted behavior when connecting to a smtp server to send emails. That method has been deprecated by pep-594 as the new python email API is capable of automatically formatting email address in a RFC compliant way. As the method was still in use in a lot of modules as the preferred way to format email addresses, a refined P3-like implementation as been included in the tool suite. This commit changes the default behavior so it mimics P2 implementation with an easy way to use the P3 behavior. Task: 2003936 --- odoo/addons/base/tests/test_mail.py | 27 ++++++++++++++++++++++++++- odoo/tools/mail.py | 11 ++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py index d47c9e6ae87..8dbb2cddb65 100644 --- a/odoo/addons/base/tests/test_mail.py +++ b/odoo/addons/base/tests/test_mail.py @@ -6,7 +6,10 @@ from unittest.mock import patch from odoo.tests.common import BaseCase from odoo.tests.common import SavepointCase -from odoo.tools import html_sanitize, append_content_to_html, plaintext2html, email_split, misc +from odoo.tools import ( + html_sanitize, append_content_to_html, plaintext2html, email_split, + misc, formataddr, +) from . import test_mail_examples @@ -328,6 +331,28 @@ class TestEmailTools(BaseCase): for text, expected in cases: self.assertEqual(email_split(text), expected, 'email_split is broken') + def test_email_formataddr(self): + email = 'joe@example.com' + cases = [ + # (name, address), charsets expected + (('', email), ['ascii', 'utf-8'], 'joe@example.com'), + (('joe', email), ['ascii', 'utf-8'], '"joe" '), + (('joe doe', email), ['ascii', 'utf-8'], '"joe doe" '), + (('joe"doe', email), ['ascii', 'utf-8'], '"joe\\"doe" '), + (('joé', email), ['ascii'], '=?utf-8?b?am/DqQ==?= '), + (('joé', email), ['utf-8'], '"joé" '), + (('', 'joé@example.com'), ['ascii', 'utf-8'], UnicodeEncodeError), # need SMTPUTF8 support + (('', 'joe@examplé.com'), ['ascii', 'utf-8'], UnicodeEncodeError), # need IDNA support + ] + + for pair, charsets, expected in cases: + for charset in charsets: + with self.subTest(pair=pair, charset=charset): + if isinstance(expected, str): + self.assertEqual(formataddr(pair, charset), expected) + else: + self.assertRaises(expected, formataddr, pair, charset) + class EmailConfigCase(SavepointCase): @patch.dict("odoo.tools.config.options", {"email_from": "settings@example.com"}) diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index 3e881d0d841..03ac8760ece 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -516,9 +516,10 @@ def email_escape_char(email_address): def decode_message_header(message, header, separator=' '): return separator.join(h for h in message.get_all(header, []) if h) -def formataddr(pair): - """Takes a 2-tuple of the form (realname, email_address) and returns - the string value suitable for an RFC 2822 From, To or Cc header. +def formataddr(pair, charset='utf-8'): + """Pretty format a 2-tuple of the form (realname, email_address). + + Set the charset to ascii to get a RFC-2822 compliant email. The email address is considered valid and is left unmodified. @@ -535,9 +536,9 @@ def formataddr(pair): address.encode('ascii') if name: try: - name.encode('ascii') + name.encode(charset) except UnicodeEncodeError: - # non-ascii name, transcode the name in a safe format + # charset mismatch, encode as utf-8/base64 # rfc2047 - MIME Message Header Extensions for Non-ASCII Text return "=?utf-8?b?{name}?= <{addr}>".format( name=base64.b64encode(name.encode('utf-8')).decode('ascii'),