From 13cedb53e8de0d9a28717afca1816d4833310dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sat, 29 Apr 2023 19:04:18 +0200 Subject: [PATCH] Convert email address to ASCII to support RFC6530 Python email library does not support non-ASCII emails so we convert email addresses to ASCII version before passing them to sendmail. Closes #10650 --- modules/account_dunning_email/account.py | 15 +++++++++++---- .../marketing_automation/marketing_automation.py | 4 ++-- modules/marketing_email/marketing.py | 10 ++++++---- modules/notification_email/notification.py | 15 ++++++++++----- trytond/trytond/ir/email_.py | 12 ++++++++---- 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/modules/account_dunning_email/account.py b/modules/account_dunning_email/account.py index cdfb413457..45256afc1d 100644 --- a/modules/account_dunning_email/account.py +++ b/modules/account_dunning_email/account.py @@ -9,11 +9,18 @@ from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval from trytond.report import get_email from trytond.sendmail import SMTPDataManager, sendmail_transactional -from trytond.tools.email_ import set_from_header +from trytond.tools.email_ import convert_ascii_email, set_from_header from trytond.transaction import Transaction from trytond.wizard import StateTransition +def _formataddr(pair): + name, address = pair + if name: + name = str(Header(name, 'utf-8')) + return formataddr((name, convert_ascii_email(address))) + + class Configuration(metaclass=PoolMeta): __name__ = 'account.configuration' @@ -117,12 +124,12 @@ class Dunning(metaclass=PoolMeta): contact = self.party.contact_mechanism_get( 'email', usage=self.level.email_contact_mechanism) if contact and contact.email: - name = str(Header(contact.name or self.party.rec_name)) - to.append(formataddr((name, contact.email))) + name = contact.name or self.party.rec_name + to.append(_formataddr((name, contact.email))) elif account_config.dunning_email_fallback: user = account_config.get_multivalue( 'dunning_email_fallback', company=self.company.id) - to.append(formataddr((self.party.rec_name, user.email))) + to.append(_formataddr((self.party.rec_name, user.email))) cc = [] bcc = [] languages = set() diff --git a/modules/marketing_automation/marketing_automation.py b/modules/marketing_automation/marketing_automation.py index 8ed4328eb6..a445d8ba8e 100644 --- a/modules/marketing_automation/marketing_automation.py +++ b/modules/marketing_automation/marketing_automation.py @@ -32,7 +32,7 @@ from trytond.pyson import Eval, If, PYSONDecoder, TimeDelta from trytond.report import Report from trytond.sendmail import SMTPDataManager, sendmail_transactional from trytond.tools import grouped_slice, reduce_ids -from trytond.tools.email_ import set_from_header +from trytond.tools.email_ import convert_ascii_email, set_from_header from trytond.transaction import Transaction from trytond.url import http_host from trytond.wsgi import Base64Converter @@ -55,7 +55,7 @@ logger = logging.getLogger(__name__) def _formataddr(name, email): if name: name = str(Header(name, 'utf-8')) - return formataddr((name, email)) + return formataddr((name, convert_ascii_email(email))) class Scenario(Workflow, ModelSQL, ModelView): diff --git a/modules/marketing_email/marketing.py b/modules/marketing_email/marketing.py index 75c048e73f..d1cb4a8bdf 100644 --- a/modules/marketing_email/marketing.py +++ b/modules/marketing_email/marketing.py @@ -31,7 +31,7 @@ from trytond.pyson import Eval from trytond.report import Report, get_email from trytond.sendmail import SMTPDataManager, sendmail_transactional from trytond.tools import grouped_slice, reduce_ids -from trytond.tools.email_ import set_from_header +from trytond.tools.email_ import convert_ascii_email, set_from_header from trytond.transaction import Transaction, inactive_records from trytond.url import http_host from trytond.wizard import Button, StateTransition, StateView, Wizard @@ -51,7 +51,7 @@ URL_OPEN = urljoin(URL_BASE, '/m/empty.gif') def _formataddr(name, email): if name: name = str(Header(name, 'utf-8')) - return formataddr((name, email)) + return formataddr((name, convert_ascii_email(email))) def _add_params(url, **params): @@ -297,7 +297,8 @@ class EmailList(DeactivableMixin, ModelSQL, ModelView): set_from_header(msg, from_cfg, from_ or from_cfg) msg['To'] = record.email msg['Subject'] = Header(title, 'utf-8') - sendmail_transactional(from_cfg, [record.email], msg) + sendmail_transactional( + from_cfg, [convert_ascii_email(record.email)], msg) def request_unsubscribe(self, email, from_=None): pool = Pool() @@ -322,7 +323,8 @@ class EmailList(DeactivableMixin, ModelSQL, ModelView): set_from_header(msg, from_cfg, from_ or from_cfg) msg['To'] = record.email msg['Subject'] = Header(title, 'utf-8') - sendmail_transactional(from_cfg, [record.email], msg) + sendmail_transactional( + from_cfg, [convert_ascii_email(record.email)], msg) class Message(Workflow, ModelSQL, ModelView): diff --git a/modules/notification_email/notification.py b/modules/notification_email/notification.py index f7a9f234a5..66036d0fcb 100644 --- a/modules/notification_email/notification.py +++ b/modules/notification_email/notification.py @@ -6,7 +6,7 @@ from email.header import Header from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart from email.mime.nonmultipart import MIMENonMultipart -from email.utils import formataddr, getaddresses, parseaddr +from email.utils import formataddr, getaddresses from genshi.template import TextTemplate @@ -17,7 +17,7 @@ from trytond.pool import Pool from trytond.pyson import Eval, TimeDelta from trytond.report import get_email from trytond.sendmail import SMTPDataManager, sendmail_transactional -from trytond.tools.email_ import set_from_header +from trytond.tools.email_ import convert_ascii_email, set_from_header from trytond.transaction import Transaction from .exceptions import TemplateError @@ -195,8 +195,12 @@ class Email(ModelSQL, ModelView): msg = content set_from_header(msg, sender, from_) - msg['To'] = ', '.join(formataddr(parseaddr(a)) for a in to) - msg['Cc'] = ', '.join(formataddr(parseaddr(a)) for a in cc) + msg['To'] = ', '.join( + formataddr((n, convert_ascii_email(a))) + for n, a in getaddresses(to)) + msg['Cc'] = ', '.join( + formataddr((n, convert_ascii_email(a))) + for n, a in getaddresses(cc)) msg['Subject'] = Header(title, 'utf-8') msg['Auto-Submitted'] = 'auto-generated' return msg, title @@ -238,7 +242,8 @@ class Email(ModelSQL, ModelView): cc, cc_languages = self._get_cc(record) bcc, bcc_languages = self._get_bcc(record) languages = to_languages | cc_languages | bcc_languages - to_addrs = [e for _, e in getaddresses(to + cc + bcc)] + to_addrs = [ + convert_ascii_email(e) for _, e in getaddresses(to + cc + bcc)] if to_addrs: msg, title = self.get_email( record, from_, to, cc, bcc, languages) diff --git a/trytond/trytond/ir/email_.py b/trytond/trytond/ir/email_.py index 3bfb5b2bf5..0fe290a7d3 100644 --- a/trytond/trytond/ir/email_.py +++ b/trytond/trytond/ir/email_.py @@ -27,7 +27,7 @@ from trytond.report import Report from trytond.rpc import RPC from trytond.sendmail import sendmail_transactional from trytond.tools import escape_wildcard -from trytond.tools.email_ import set_from_header +from trytond.tools.email_ import convert_ascii_email, set_from_header from trytond.tools.string_ import StringMatcher from trytond.transaction import Transaction @@ -56,7 +56,7 @@ def _get_emails(value): def _formataddr(pair): "Format address without encoding" name, address = pair - address.encode('ascii') + convert_ascii_email(address).encode('ascii') if name: quotes = '' if specialsre.search(name): @@ -167,8 +167,12 @@ class Email(ResourceAccessMixin, ModelSQL, ModelView): msg = content from_ = config.get('email', 'from') set_from_header(msg, from_, user.email or from_) - msg['To'] = ', '.join(formataddr(a) for a in getaddresses([to])) - msg['Cc'] = ', '.join(formataddr(a) for a in getaddresses([cc])) + msg['To'] = ', '.join( + formataddr((n, convert_ascii_email(a))) + for n, a in getaddresses([to])) + msg['Cc'] = ', '.join( + formataddr((n, convert_ascii_email(a))) + for n, a in getaddresses([cc])) msg['Subject'] = Header(subject, 'utf-8') to_addrs = list(filter(None, map(