Commit 64bc6838 authored by Romain Courteaud's avatar Romain Courteaud

MailTemplates: improve email header encoding

Use MIME encoded words also for names.
https://en.wikipedia.org/wiki/Email#Message_header
parent 073f6cf1
...@@ -34,6 +34,8 @@ from DateTime import DateTime ...@@ -34,6 +34,8 @@ from DateTime import DateTime
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from Products.ERP5Type.tests.Sequence import SequenceList from Products.ERP5Type.tests.Sequence import SequenceList
from Products.ERP5Type.tests.utils import DummyMailHost from Products.ERP5Type.tests.utils import DummyMailHost
from email import message_from_string
from email.header import decode_header
class TestBug(ERP5TypeTestCase): class TestBug(ERP5TypeTestCase):
...@@ -215,7 +217,8 @@ class TestBug(ERP5TypeTestCase): ...@@ -215,7 +217,8 @@ class TestBug(ERP5TypeTestCase):
mfrom, mto, messageText = last_message mfrom, mto, messageText = last_message
self.assertEqual('dummy <loggedperson@localhost>', mfrom) self.assertEqual('dummy <loggedperson@localhost>', mfrom)
self.assertEqual(['person1@localhost'], mto) self.assertEqual(['person1@localhost'], mto)
self.assertTrue(bug.getTitle().replace(" ", "_") in messageText) message = message_from_string(messageText)
self.assertTrue(decode_header(message['Subject'])[0][0].endswith(bug.getTitle()))
def stepCheckBugMessageNotification(self, sequence=None, def stepCheckBugMessageNotification(self, sequence=None,
sequence_list=None, **kw): sequence_list=None, **kw):
...@@ -228,7 +231,8 @@ class TestBug(ERP5TypeTestCase): ...@@ -228,7 +231,8 @@ class TestBug(ERP5TypeTestCase):
mfrom, mto, messageText = last_message mfrom, mto, messageText = last_message
self.assertEqual('person2@localhost', mfrom) self.assertEqual('person2@localhost', mfrom)
self.assertEqual(['person1@localhost'], mto) self.assertEqual(['person1@localhost'], mto)
self.assertTrue(bug.getTitle().replace(" ", "_") in messageText) message = message_from_string(messageText)
self.assertTrue(decode_header(message['Subject'])[0][0].endswith(bug.getTitle()))
def stepSetSourceProject(self, sequence=None, sequence_list=None, **kw): def stepSetSourceProject(self, sequence=None, sequence_list=None, **kw):
""" """
......
...@@ -995,6 +995,13 @@ class TestCRMMailSend(BaseTestCRM): ...@@ -995,6 +995,13 @@ class TestCRMMailSend(BaseTestCRM):
title='Recipient,', title='Recipient,',
subordination_value=customer_organisation, subordination_value=customer_organisation,
default_email_text='recipient@example.com') default_email_text='recipient@example.com')
portal.person_module.newContent(
id='non_ascii_recipient',
# The ',' below is to force quoting of the name in e-mail
# addresses on Zope 2.12
title='Recipient, 🐈 fan',
subordination_value=customer_organisation,
default_email_text='recipient@example.com')
# also create the sender # also create the sender
portal.person_module.newContent( portal.person_module.newContent(
id='me', id='me',
...@@ -1002,6 +1009,12 @@ class TestCRMMailSend(BaseTestCRM): ...@@ -1002,6 +1009,12 @@ class TestCRMMailSend(BaseTestCRM):
# addresses on Zope 2.12 # addresses on Zope 2.12
title='Me,', title='Me,',
default_email_text='me@erp5.org') default_email_text='me@erp5.org')
portal.person_module.newContent(
id='non_ascii_me',
# The ',' below is to force quoting of the name in e-mail
# addresses on Zope 2.12
title='Me, 🐈 fan',
default_email_text='me@erp5.org')
# set preference # set preference
default_pref = self.portal.portal_preferences.default_site_preference default_pref = self.portal.portal_preferences.default_site_preference
...@@ -1128,8 +1141,8 @@ class TestCRMMailSend(BaseTestCRM): ...@@ -1128,8 +1141,8 @@ class TestCRMMailSend(BaseTestCRM):
def test_MailMessageEncoding(self): def test_MailMessageEncoding(self):
# test sending a mail message with non ascii characters # test sending a mail message with non ascii characters
event = self.portal.event_module.newContent(portal_type='Mail Message') event = self.portal.event_module.newContent(portal_type='Mail Message')
event.setSource('person_module/me') event.setSource('person_module/non_ascii_me')
event.setDestination('person_module/recipient') event.setDestinationList(['person_module/non_ascii_recipient'])
event.setTitle('Héhé') event.setTitle('Héhé')
event.setTextContent('Hàhà') event.setTextContent('Hàhà')
self.portal.portal_workflow.doActionFor(event, 'start_action') self.portal.portal_workflow.doActionFor(event, 'start_action')
...@@ -1137,12 +1150,14 @@ class TestCRMMailSend(BaseTestCRM): ...@@ -1137,12 +1150,14 @@ class TestCRMMailSend(BaseTestCRM):
last_message = self.portal.MailHost._last_message last_message = self.portal.MailHost._last_message
self.assertNotEquals((), last_message) self.assertNotEquals((), last_message)
mfrom, mto, messageText = last_message mfrom, mto, messageText = last_message
self.assertEqual('"Me," <me@erp5.org>', mfrom) self.assertEqual('=?utf-8?q?Me=2C_=F0=9F=90=88_fan?= <me@erp5.org>', mfrom)
self.assertEqual(['"Recipient," <recipient@example.com>'], mto) self.assertEqual(['=?utf-8?q?Recipient=2C_=F0=9F=90=88_fan?= <recipient@example.com>'], mto)
message = message_from_string(messageText) message = message_from_string(messageText)
self.assertEqual('Héhé', decode_header(message['Subject'])[0][0]) self.assertEqual('Héhé', decode_header(message['Subject'])[0][0])
self.assertEqual('Me, 🐈 fan', decode_header(message['From'])[0][0])
self.assertEqual('Recipient, 🐈 fan', decode_header(message['To'])[0][0])
part = None part = None
for i in message.get_payload(): for i in message.get_payload():
if i.get_content_type()=='text/plain': if i.get_content_type()=='text/plain':
...@@ -1756,7 +1771,7 @@ class TestCRMMailSend(BaseTestCRM): ...@@ -1756,7 +1771,7 @@ class TestCRMMailSend(BaseTestCRM):
self.tic() self.tic()
mail_message = self.portal.event_module.newContent(portal_type="Mail Message") mail_message = self.portal.event_module.newContent(portal_type="Mail Message")
relative_url_list = [z.getRelativeUrl() for z in self.portal.person_module.searchFolder()] relative_url_list = [z.getRelativeUrl() for z in self.portal.person_module.searchFolder()]
self.assertEqual(3, len(relative_url_list)) self.assertEqual(5, len(relative_url_list))
mail_message.setDestinationList(relative_url_list) mail_message.setDestinationList(relative_url_list)
mail_message.setSource(relative_url_list[0]) mail_message.setSource(relative_url_list[0])
mail_text_content = "Body Text Content" mail_text_content = "Body Text Content"
...@@ -1774,12 +1789,12 @@ class TestCRMMailSend(BaseTestCRM): ...@@ -1774,12 +1789,12 @@ class TestCRMMailSend(BaseTestCRM):
message_list = [i for i in portal_activities.getMessageList() \ message_list = [i for i in portal_activities.getMessageList() \
if i.kw.has_key("event_relative_url")] if i.kw.has_key("event_relative_url")]
try: try:
# 3 recipients -> 3 activities # 5 recipients -> 5 activities
self.assertEqual(3, len(message_list)) self.assertEqual(5, len(message_list))
finally: finally:
self.tic() self.tic()
self.assertEqual(3, len(self.portal.MailHost._message_list)) self.assertEqual(5, len(self.portal.MailHost._message_list))
for message_info in self.portal.MailHost._message_list: for message_info in self.portal.MailHost._message_list:
self.assertTrue(mail_text_content in message_info[-1]) self.assertTrue(mail_text_content in message_info[-1])
message = message_from_string(message_info[-1]) message = message_from_string(message_info[-1])
......
...@@ -15,8 +15,8 @@ try: ...@@ -15,8 +15,8 @@ try:
except ImportError: except ImportError:
BaseMailTemplate = None BaseMailTemplate = None
from email.Header import make_header from email.Header import Header
from email.Utils import make_msgid from email.Utils import make_msgid, formataddr, getaddresses
if BaseMailTemplate is not None: if BaseMailTemplate is not None:
def _process_utf8(self,kw): def _process_utf8(self,kw):
...@@ -60,14 +60,32 @@ if BaseMailTemplate is not None: ...@@ -60,14 +60,32 @@ if BaseMailTemplate is not None:
headers.get(header)))) headers.get(header))))
if value is not None: if value is not None:
values[key]=value values[key]=value
# turn some sequences in coma-seperated strings # turn some sequences in coma-seperated strings
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
value = ', '.join(value) value = ', '.join(value)
# make sure we have no unicode headers # make sure we have no unicode headers
if isinstance(value,unicode): if isinstance(value,unicode):
value = value.encode(encoding) value = value.encode(encoding)
if key=='subject':
value = make_header([(value, 'utf-8')]).encode() if key == 'subject':
try:
# Try to keep header non encoded
value = Header(value.encode("ascii"))
except UnicodeDecodeError:
value = Header(value, "UTF-8")
else:
value_list = getaddresses([value])
dest_list = []
for name, email in value_list:
try:
name = Header(name.encode("ascii"))
except UnicodeDecodeError:
name = Header(name, "UTF-8")
dest_list.append(formataddr((name.encode(), email)))
value = ", ".join(dest_list)
headers[header]=value headers[header]=value
# check required values have been supplied # check required values have been supplied
errors = [] errors = []
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment