cpython: 30795a477f85 (original) (raw)
Mercurial > cpython
changeset 96126:30795a477f85
#24218: Add SMTPUTF8 support to send_message. Reviewed by Maciej Szulik. [#24218]
R David Murray rdmurray@bitdance.com | |
---|---|
date | Sun, 17 May 2015 19:27:22 -0400 |
parents | 257ea04df092 |
children | 4a254750ad20 |
files | Doc/library/smtplib.rst Doc/whatsnew/3.5.rst Lib/smtplib.py Lib/test/test_smtplib.py |
diffstat | 4 files changed, 86 insertions(+), 8 deletions(-)[+] [-] Doc/library/smtplib.rst 12 Doc/whatsnew/3.5.rst 6 Lib/smtplib.py 29 Lib/test/test_smtplib.py 47 |
line wrap: on
line diff
--- a/Doc/library/smtplib.rst
+++ b/Doc/library/smtplib.rst
@@ -467,7 +467,7 @@ An :class:SMTP
instance has the follow
If from_addr is None
or to_addrs is None
, send_message
fills
those arguments with addresses extracted from the headers of msg as
- specified in :rfc:
5322
: from_addr is set to the :mailheader:Sender
field if it is present, and otherwise to the :mailheader:From
field. to_adresses combines the values (if any) of the :mailheader:To
, :mailheader:Cc
, and :mailheader:Bcc
fields from msg. If exactly one @@ -482,10 +482,18 @@ An :class:SMTP
instance has the follow calls :meth:sendmail
to transmit the resulting message. Regardless of the values of from_addr and to_addrs,send_message
does not transmit any :mailheader:Bcc
or :mailheader:Resent-Bcc
headers that may appear
- in msg. If any of the addresses in from_addr and to_addrs contain
- non-ASCII characters and the server does not advertise
SMTPUTF8
support, - an :exc:
SMTPNotSupported
error is raised. Otherwise theMessage
is - serialized with a clone of its :mod:
~email.policy
with the - :attr:
~email.policy.EmailPolicy.utf8
attribute set toTrue
, and SMTPUTF8
andBODY=8BITMIME
are added to mail_options. .. versionadded:: 3.2- .. versionadded:: 3.5
Support for internationalized addresses (``SMTPUTF8``).[](#l1.27)
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -557,8 +557,10 @@ smtplib
:class:smtplib.SMTP
. (Contributed by Gavin Chappell and Maciej Szulik in
:issue:16914
.)
-* :mod:smtplib
now support :rfc:6531
(SMTPUTF8). (Contributed by
- Milan Oberkirch and R. David Murray in :issue:
22027
.) +* :mod:smtplib
now supports :rfc:6531
(SMTPUTF8) in both the
- :meth:
~smtplib.SMTP.sendmail
and :meth:~smtplib.SMTP.send_message
- commands. (Contributed by Milan Oberkirch and R. David Murray in
- :issue:
22027
.) sndhdr ------
--- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -872,7 +872,13 @@ class SMTP: to_addr, any Bcc field (or Resent-Bcc field, when the Message is a resent) of the Message object won't be transmitted. The Message object is then serialized using email.generator.BytesGenerator and
sendmail is called to transmit the message.[](#l3.7)
sendmail is called to transmit the message. If the sender or any of[](#l3.8)
the recipient addresses contain non-ASCII and the server advertises the[](#l3.9)
SMTPUTF8 capability, the policy is cloned with utf8 set to True for the[](#l3.10)
serialization, and SMTPUTF8 and BODY=8BITMIME are asserted on the send.[](#l3.11)
If the server does not support SMTPUTF8, an SMPTNotSupported error is[](#l3.12)
raised. Otherwise the generator is called without modifying the[](#l3.13)
policy.[](#l3.14)
""" # 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822 @@ -885,6 +891,7 @@ class SMTP: # option allowing the user to enable the heuristics. (It should be # possible to guess correctly almost all of the time.)
self.ehlo_or_helo_if_needed()[](#l3.22) resent = msg.get_all('Resent-Date')[](#l3.23) if resent is None:[](#l3.24) header_prefix = ''[](#l3.25)
@@ -900,14 +907,30 @@ class SMTP: if to_addrs is None: addr_fields = [f for f in (msg[header_prefix + 'To'], msg[header_prefix + 'Bcc'],
msg[header_prefix + 'Cc']) if f is not None][](#l3.30)
msg[header_prefix + 'Cc'])[](#l3.31)
if f is not None][](#l3.32) to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)][](#l3.33) # Make a local copy so we can delete the bcc headers.[](#l3.34) msg_copy = copy.copy(msg)[](#l3.35) del msg_copy['Bcc'][](#l3.36) del msg_copy['Resent-Bcc'][](#l3.37)
international = False[](#l3.38)
try:[](#l3.39)
''.join([from_addr, *to_addrs]).encode('ascii')[](#l3.40)
except UnicodeEncodeError:[](#l3.41)
if not self.has_extn('smtputf8'):[](#l3.42)
raise SMTPNotSupportedError([](#l3.43)
"One or more source or delivery addresses require"[](#l3.44)
" internationalized email support, but the server"[](#l3.45)
" does not advertise the required SMTPUTF8 capability")[](#l3.46)
international = True[](#l3.47) with io.BytesIO() as bytesmsg:[](#l3.48)
g = email.generator.BytesGenerator(bytesmsg)[](#l3.49)
if international:[](#l3.50)
g = email.generator.BytesGenerator([](#l3.51)
bytesmsg, policy=msg.policy.clone(utf8=True))[](#l3.52)
mail_options += ['SMTPUTF8', 'BODY=8BITMIME'][](#l3.53)
else:[](#l3.54)
g = email.generator.BytesGenerator(bytesmsg)[](#l3.55) g.flatten(msg_copy, linesep='\r\n')[](#l3.56) flatmsg = bytesmsg.getvalue()[](#l3.57) return self.sendmail(from_addr, to_addrs, flatmsg, mail_options,[](#l3.58)
--- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -1,5 +1,6 @@ import asyncore import email.mime.text +from email.message import EmailMessage import email.utils import socket import smtpd @@ -10,7 +11,7 @@ import sys import time import select import errno -import base64 +import textwrap import unittest from test import support, mock_socket @@ -1029,6 +1030,8 @@ class SimSMTPUTF8Server(SimSMTPServer): @unittest.skipUnless(threading, 'Threading required for this test.') class SMTPUTF8SimTests(unittest.TestCase):
+ def setUp(self): self.real_getfqdn = socket.getfqdn socket.getfqdn = mock_socket.getfqdn @@ -1096,6 +1099,48 @@ class SMTPUTF8SimTests(unittest.TestCase self.assertIn('SMTPUTF8', self.serv.last_mail_options) self.assertEqual(self.serv.last_rcpt_options, [])
- def test_send_message_uses_smtputf8_if_addrs_non_ascii(self):
msg = EmailMessage()[](#l4.33)
msg['From'] = "Páolo <főo@bar.com>"[](#l4.34)
msg['To'] = 'Dinsdale'[](#l4.35)
msg['Subject'] = 'Nudge nudge, wink, wink \u1F609'[](#l4.36)
# XXX I don't know why I need two \n's here, but this is an existing[](#l4.37)
# bug (if it is one) and not a problem with the new functionality.[](#l4.38)
msg.set_content("oh là là, know what I mean, know what I mean?\n\n")[](#l4.39)
# XXX smtpd converts received /r/n to /n, so we can't easily test that[](#l4.40)
# we are successfully sending /r/n :(.[](#l4.41)
expected = textwrap.dedent("""\[](#l4.42)
From: Páolo <főo@bar.com>[](#l4.43)
To: Dinsdale[](#l4.44)
Subject: Nudge nudge, wink, wink \u1F609[](#l4.45)
Content-Type: text/plain; charset="utf-8"[](#l4.46)
Content-Transfer-Encoding: 8bit[](#l4.47)
MIME-Version: 1.0[](#l4.48)
oh là là, know what I mean, know what I mean?[](#l4.50)
""")[](#l4.51)
smtp = smtplib.SMTP([](#l4.52)
HOST, self.port, local_hostname='localhost', timeout=3)[](#l4.53)
self.addCleanup(smtp.close)[](#l4.54)
self.assertEqual(smtp.send_message(msg), {})[](#l4.55)
self.assertEqual(self.serv.last_mailfrom, 'főo@bar.com')[](#l4.56)
self.assertEqual(self.serv.last_rcpttos, ['Dinsdale'])[](#l4.57)
self.assertEqual(self.serv.last_message.decode(), expected)[](#l4.58)
self.assertIn('BODY=8BITMIME', self.serv.last_mail_options)[](#l4.59)
self.assertIn('SMTPUTF8', self.serv.last_mail_options)[](#l4.60)
self.assertEqual(self.serv.last_rcpt_options, [])[](#l4.61)
- def test_send_message_error_on_non_ascii_addrs_if_no_smtputf8(self):
msg = EmailMessage()[](#l4.64)
msg['From'] = "Páolo <főo@bar.com>"[](#l4.65)
msg['To'] = 'Dinsdale'[](#l4.66)
msg['Subject'] = 'Nudge nudge, wink, wink \u1F609'[](#l4.67)
smtp = smtplib.SMTP([](#l4.68)
HOST, self.port, local_hostname='localhost', timeout=3)[](#l4.69)
self.addCleanup(smtp.close)[](#l4.70)
self.assertRaises(smtplib.SMTPNotSupportedError,[](#l4.71)
smtp.send_message(msg))[](#l4.72)