From d7c823c519a8158127626e259941c53ae7c7a26a Mon Sep 17 00:00:00 2001 From: Konstantin Ryabitsev Date: Mon, 1 Nov 2021 11:46:10 -0400 Subject: ty: fix problems with smtplib and 8BITMIME There appears to be a bug in smtplib that doesn't properly support 8-bit content even when upstream SMTP gateway supports 8BITMIME (they all do, it's not 90s any more). Work around this by passing the message payload as bytes instead of string. Signed-off-by: Konstantin Ryabitsev --- b4/__init__.py | 1 + b4/ty.py | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/b4/__init__.py b/b4/__init__.py index adbcf7d..14224a7 100644 --- a/b4/__init__.py +++ b/b4/__init__.py @@ -2505,6 +2505,7 @@ def get_smtp(identity: Optional[str] = None): raise smtplib.SMTPException('Invalid smtpport entry in %s' % sectname) encryption = sconfig.get('smtpencryption') + logger.info('Connecting to %s:%s', server, port) # We only authenticate if we have encryption if encryption: if encryption in ('tls', 'starttls'): diff --git a/b4/ty.py b/b4/ty.py index 874fa5e..1f4847b 100644 --- a/b4/ty.py +++ b/b4/ty.py @@ -11,6 +11,7 @@ import b4 import re import email import email.message +import email.policy import json from string import Template @@ -91,9 +92,9 @@ def make_reply(reply_template, jsondata): # Add original sender to the To allto.append((jsondata['fromname'], jsondata['fromemail'])) - msg['To'] = b4.format_addrs(allto) + msg.add_header('To', b4.format_addrs(allto)) if allcc: - msg['Cc'] = b4.format_addrs(allcc) + msg.add_header('Cc', b4.format_addrs(allcc)) msg['In-Reply-To'] = '<%s>' % jsondata['msgid'] if len(jsondata['references']): msg['References'] = '%s <%s>' % (jsondata['references'], jsondata['msgid']) @@ -102,9 +103,9 @@ def make_reply(reply_template, jsondata): subject = re.sub(r'^Re:\s+', '', jsondata['subject'], flags=re.I) if jsondata.get('cherrypick'): - msg['Subject'] = 'Re: (subset) ' + subject + msg.add_header('Subject', 'Re: (subset) ' + subject) else: - msg['Subject'] = 'Re: ' + subject + msg.add_header('Subject', 'Re: ' + subject) mydomain = jsondata['myemail'].split('@')[1] msg['Message-Id'] = email.utils.make_msgid(idstring='b4-ty', domain=mydomain) @@ -427,20 +428,29 @@ def send_messages(listing, branch, cmdargs): msg.set_charset('utf-8') msg.replace_header('Content-Transfer-Encoding', '8bit') if cmdargs.sendemail: + msg.policy = email.policy.EmailPolicy(utf8=True, cte_type='8bit') + emldata = msg.as_string() if not fromaddr: fromaddr = jsondata['myemail'] if cmdargs.dryrun: logger.info('--- DRYRUN: message follows ---') - emldata = msg.as_string(policy=b4.emlpolicy) logger.info('\t' + emldata.replace('\n', '\n\t')) logger.info('--- DRYRUN: message ends ---') else: alldests = email.utils.getaddresses([str(x) for x in msg.get_all('to', [])]) alldests += email.utils.getaddresses([str(x) for x in msg.get_all('cc', [])]) sendto = {x[1] for x in alldests} - logger.info('Sending: %s', msg.get('subject')) - mypolicy = email.policy.EmailPolicy(utf8=True, cte_type='8bit') - smtp.sendmail(fromaddr, sendto, msg.as_string(policy=mypolicy)) # noqa + logger.info(' Sending: %s', msg.get('subject')) + # Python's sendmail implementation seems to have some logic problems where 8-bit messages are involved. + # As far as I understand the difference between 8BITMIME (supported by nearly all smtp servers) and + # SMTPUTF8 (supported by very few), SMTPUTF8 is only required when the addresses specified in either + # "MAIL FROM" or "RCPT TO" lines of the _protocol exchange_ themselves have 8bit characters, not + # anything in the From: header of the DATA payload. Python's smtplib seems to always try to encode + # strings as ascii regardless of what was policy was specified. + # Work around this by getting the payload as string and then encoding to bytes ourselves. + # Force compliant eols + emldata = re.sub(r'(?:\r\n|\n|\r(?!\n))', '\r\n', emldata) # noqa + smtp.sendmail(fromaddr, sendto, emldata.encode()) # noqa else: slug_from = re.sub(r'\W', '_', jsondata['fromemail']) slug_subj = re.sub(r'\W', '_', jsondata['subject']) -- cgit v1.2.3