-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[IMP] ir_mail_server: IDNA and SMTPUTF8 capabilities
It has been a recurrent request from customers to be able to send email messages to email addresses containing non-ascii characters. [IDNA] is a domain extension to allow unicode characters in domain names. [SMTPUTF8] is a SMTP extension to allow unicode in any header. IDNA defines the [punycode] encoding which translates unicode to an ascii representation. This encoding MUST be used to encode domains. SMTPUTF8 is an SMTP extension that allow utf-8 in all headers on the envelope. [IDNA] https://tools.ietf.org/html/rfc5890 [SMTPUTF8] https://tools.ietf.org/html/rfc6531 [punycode] https://tools.ietf.org/html/rfc3492 Task: 2116928 opw-2229906 opw-2248251 closes odoo#47709 Signed-off-by: Raphael Collet (rco) <[email protected]>
- Loading branch information
1 parent
89c1d81
commit afcb734
Showing
7 changed files
with
58 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,16 +25,6 @@ def test_mail_message_notify_from_mail_mail(self): | |
self.assertSentEmail(mail.env.user.partner_id, ['[email protected]']) | ||
self.assertEqual(len(self._mails), 1) | ||
|
||
@mute_logger('odoo.addons.mail.models.mail_mail') | ||
def test_mail_message_values_unicode(self): | ||
mail = self.env['mail.mail'].sudo().create({ | ||
'body_html': '<p>Test</p>', | ||
'email_to': 'test.😊@example.com', | ||
'partner_ids': [(4, self.user_employee.partner_id.id)] | ||
}) | ||
|
||
self.assertRaises(MailDeliveryException, lambda: mail.send(raise_exception=True)) | ||
|
||
|
||
class TestMailMailRace(common.TransactionCase): | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -351,6 +351,7 @@ def test_email_split(self): | |
|
||
def test_email_formataddr(self): | ||
email = '[email protected]' | ||
email_idna = 'joe@examplé.com' | ||
cases = [ | ||
# (name, address), charsets expected | ||
(('', email), ['ascii', 'utf-8'], '[email protected]'), | ||
|
@@ -359,17 +360,17 @@ def test_email_formataddr(self): | |
(('joe"doe', email), ['ascii', 'utf-8'], '"joe\\"doe" <[email protected]>'), | ||
(('joé', email), ['ascii'], '=?utf-8?b?am/DqQ==?= <[email protected]>'), | ||
(('joé', email), ['utf-8'], '"joé" <[email protected]>'), | ||
(('', 'joé@example.com'), ['ascii', 'utf-8'], UnicodeEncodeError), # need SMTPUTF8 support | ||
(('', 'joe@examplé.com'), ['ascii', 'utf-8'], UnicodeEncodeError), # need IDNA support | ||
(('', email_idna), ['ascii'], '[email protected]'), | ||
(('', email_idna), ['utf-8'], 'joe@examplé.com'), | ||
(('joé', email_idna), ['ascii'], '=?utf-8?b?am/DqQ==?= <[email protected]>'), | ||
(('joé', email_idna), ['utf-8'], '"joé" <joe@examplé.com>'), | ||
(('', 'joé@example.com'), ['ascii', 'utf-8'], 'joé@example.com'), | ||
] | ||
|
||
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) | ||
self.assertEqual(formataddr(pair, charset), expected) | ||
|
||
|
||
class EmailConfigCase(SavepointCase): | ||
|
@@ -403,7 +404,19 @@ class FakeSMTP: | |
def __init__(this): | ||
this.email_sent = False | ||
|
||
def sendmail(this, smtp_from, smtp_to_list, message_str): | ||
def sendmail(this, smtp_from, smtp_to_list, message_str, | ||
mail_options=(), rcpt_options=()): | ||
this.email_sent = True | ||
message_truth = ( | ||
r'From: .+? <joe@example\.com>\r\n' | ||
r'To: .+? <joe@example\.com>\r\n' | ||
r'\r\n' | ||
) | ||
self.assertRegex(message_str, message_truth) | ||
|
||
def send_message(this, message, smtp_from, smtp_to_list, | ||
mail_options=(), rcpt_options=()): | ||
message_str = message.as_string() | ||
this.email_sent = True | ||
message_truth = ( | ||
r'From: .+? <joe@example\.com>\r\n' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
from email.utils import getaddresses | ||
from lxml import etree | ||
from werkzeug import urls | ||
import idna | ||
|
||
import odoo | ||
from odoo.loglevels import ustr | ||
|
@@ -546,35 +547,42 @@ def decode_message_header(message, header, separator=' '): | |
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. | ||
If the first element of pair is falsy then only the email address | ||
is returned. | ||
Set the charset to ascii to get a RFC-2822 compliant email. The | ||
realname will be base64 encoded (if necessary) and the domain part | ||
of the email will be punycode encoded (if necessary). The local part | ||
is left unchanged thus require the SMTPUTF8 extension when there are | ||
non-ascii characters. | ||
>>> formataddr(('John Doe', '[email protected]')) | ||
'"John Doe" <[email protected]>' | ||
>>> formataddr(('', '[email protected]')) | ||
'[email protected]' | ||
""" | ||
name, address = pair | ||
address.encode('ascii') | ||
local, _, domain = address.rpartition('@') | ||
|
||
try: | ||
domain.encode(charset) | ||
except UnicodeEncodeError: | ||
# rfc5890 - Internationalized Domain Names for Applications (IDNA) | ||
domain = idna.encode(domain).decode('ascii') | ||
|
||
if name: | ||
try: | ||
name.encode(charset) | ||
except UnicodeEncodeError: | ||
# 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'), | ||
addr=address) | ||
name = base64.b64encode(name.encode('utf-8')).decode('ascii') | ||
return f"=?utf-8?b?{name}?= <{local}@{domain}>" | ||
else: | ||
# ascii name, escape it if needed | ||
# rfc2822 - Internet Message Format | ||
# #section-3.4 - Address Specification | ||
return '"{name}" <{addr}>'.format( | ||
name=email_addr_escapes_re.sub(r'\\\g<0>', name), | ||
addr=address) | ||
return address | ||
name = email_addr_escapes_re.sub(r'\\\g<0>', name) | ||
return f'"{name}" <{local}@{domain}>' | ||
return f"{local}@{domain}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters