diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index 9c72e54fdbb..ad8f03ca637 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -33,6 +33,21 @@ def _print_debug(self, *args): _logger.debug(' '.join(str(a) for a in args)) smtplib.SMTP._print_debug = _print_debug +# Python 3: workaround for bpo-35805, only partially fixed in Python 3.8. +RFC5322_IDENTIFICATION_HEADERS = {'message-id', 'in-reply-to', 'references', 'resent-msg-id'} +_noFoldPolicy = email.policy.SMTP.clone(max_line_length=None) +class IdentificationFieldsNoFoldPolicy(email.policy.EmailPolicy): + # Override _fold() to avoid folding identification fields, excluded by RFC2047 section 5 + # These are particularly important to preserve, as MTAs will often rewrite non-conformant + # Message-ID headers, causing a loss of thread information (replies are lost) + def _fold(self, name, value, *args, **kwargs): + if name.lower() in RFC5322_IDENTIFICATION_HEADERS: + return _noFoldPolicy._fold(name, value, *args, **kwargs) + return super()._fold(name, value, *args, **kwargs) + +# Global monkey-patch for our preferred SMTP policy, preserving the non-default linesep +email.policy.SMTP = IdentificationFieldsNoFoldPolicy(linesep=email.policy.SMTP.linesep) + # Python 2: replace smtplib's stderr class WriteToLogger(object): def write(self, s): diff --git a/odoo/addons/base/tests/test_mail.py b/odoo/addons/base/tests/test_mail.py index 69ad772447e..9f85bad64f4 100644 --- a/odoo/addons/base/tests/test_mail.py +++ b/odoo/addons/base/tests/test_mail.py @@ -450,38 +450,42 @@ class EmailConfigCase(SavepointCase): class TestEmailMessage(TransactionCase): def test_as_string(self): - """Ensure all email sent are bpo-34424 free""" + """Ensure all email sent are bpo-34424 and bpo-35805 free""" + + message_truth = ( + r'From: .+? \r\n' + r'To: .+? \r\n' + r'Message-Id: <[0-9a-z.-]+@[0-9a-z.-]+>\r\n' + r'References: (<[0-9a-z.-]+@[0-9a-z.-]+>\s*)+\r\n' + r'\r\n' + ) class FakeSMTP: """SMTP stub""" def __init__(this): this.email_sent = False + # Python 3 before 3.7.4 def sendmail(this, smtp_from, smtp_to_list, message_str, mail_options=(), rcpt_options=()): this.email_sent = True - message_truth = ( - r'From: .+? \r\n' - r'To: .+? \r\n' - r'\r\n' - ) self.assertRegex(message_str, message_truth) + # Python 3.7.4+ 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: .+? \r\n' - r'To: .+? \r\n' - r'\r\n' - ) self.assertRegex(message_str, message_truth) msg = email.message.EmailMessage(policy=email.policy.SMTP) msg['From'] = '"Joé Doe" ' msg['To'] = '"Joé Doe" ' + # Message-Id & References fields longer than 77 chars (bpo-35805) + msg['Message-Id'] = '<929227342217024.1596730490.324691772460938-example-30661-some.reference@test-123.example.com>' + msg['References'] = '<345227342212345.1596730777.324691772483620-example-30453-other.reference@test-123.example.com>' + smtp = FakeSMTP() self.patch(threading.currentThread(), 'testing', False) self.env['ir.mail_server'].send_email(msg, smtp_session=smtp)