Issue #27033: The default value of the decode_data parameter for
smtpd.SMTPChannel and smtpd.SMTPServer constructors is changed to False.
This commit is contained in:
parent
4ecfa455ae
commit
cbcc2fd641
|
@ -29,7 +29,7 @@ SMTPServer Objects
|
||||||
|
|
||||||
|
|
||||||
.. class:: SMTPServer(localaddr, remoteaddr, data_size_limit=33554432,\
|
.. class:: SMTPServer(localaddr, remoteaddr, data_size_limit=33554432,\
|
||||||
map=None, enable_SMTPUTF8=False, decode_data=True)
|
map=None, enable_SMTPUTF8=False, decode_data=False)
|
||||||
|
|
||||||
Create a new :class:`SMTPServer` object, which binds to local address
|
Create a new :class:`SMTPServer` object, which binds to local address
|
||||||
*localaddr*. It will treat *remoteaddr* as an upstream SMTP relayer. It
|
*localaddr*. It will treat *remoteaddr* as an upstream SMTP relayer. It
|
||||||
|
@ -45,20 +45,19 @@ SMTPServer Objects
|
||||||
global socket map is used.
|
global socket map is used.
|
||||||
|
|
||||||
*enable_SMTPUTF8* determins whether the ``SMTPUTF8`` extension (as defined
|
*enable_SMTPUTF8* determins whether the ``SMTPUTF8`` extension (as defined
|
||||||
in :RFC:`6531`) should be enabled. The default is ``False``. If set to
|
in :RFC:`6531`) should be enabled. The default is ``False``.
|
||||||
``True``, *decode_data* must be ``False`` (otherwise an error is raised).
|
|
||||||
When ``True``, ``SMTPUTF8`` is accepted as a parameter to the ``MAIL``
|
When ``True``, ``SMTPUTF8`` is accepted as a parameter to the ``MAIL``
|
||||||
command and when present is passed to :meth:`process_message` in the
|
command and when present is passed to :meth:`process_message` in the
|
||||||
``kwargs['mail_options']`` list.
|
``kwargs['mail_options']`` list. *decode_data* and *enable_SMTPUTF8*
|
||||||
|
cannot be set to ``True`` at the same time.
|
||||||
|
|
||||||
*decode_data* specifies whether the data portion of the SMTP transaction
|
*decode_data* specifies whether the data portion of the SMTP transaction
|
||||||
should be decoded using UTF-8. The default is ``True`` for backward
|
should be decoded using UTF-8. The default is ``False``. When
|
||||||
compatibility reasons, but will change to ``False`` in Python 3.6; specify
|
*decode_data* is not set to ``True`` the server advertises the ``8BITMIME``
|
||||||
the keyword value explicitly to avoid the :exc:`DeprecationWarning`. When
|
|
||||||
*decode_data* is set to ``False`` the server advertises the ``8BITMIME``
|
|
||||||
extension (:rfc:`6152`), accepts the ``BODY=8BITMIME`` parameter to
|
extension (:rfc:`6152`), accepts the ``BODY=8BITMIME`` parameter to
|
||||||
the ``MAIL`` command, and when present passes it to :meth:`process_message`
|
the ``MAIL`` command, and when present passes it to :meth:`process_message`
|
||||||
in the ``kwargs['mail_options']`` list.
|
in the ``kwargs['mail_options']`` list. *decode_data* and *enable_SMTPUTF8*
|
||||||
|
cannot be set to ``True`` at the same time.
|
||||||
|
|
||||||
.. method:: process_message(peer, mailfrom, rcpttos, data, **kwargs)
|
.. method:: process_message(peer, mailfrom, rcpttos, data, **kwargs)
|
||||||
|
|
||||||
|
@ -71,13 +70,12 @@ SMTPServer Objects
|
||||||
format).
|
format).
|
||||||
|
|
||||||
If the *decode_data* constructor keyword is set to ``True``, the *data*
|
If the *decode_data* constructor keyword is set to ``True``, the *data*
|
||||||
argument will be a unicode string. If it is set to ``False``, it
|
parameter will be a unicode string. If it is set to ``False``, it
|
||||||
will be a bytes object.
|
will be a bytes object.
|
||||||
|
|
||||||
*kwargs* is a dictionary containing additional information. It is empty
|
*kwargs* is a dictionary containing additional information. It is empty
|
||||||
unless at least one of ``decode_data=False`` or ``enable_SMTPUTF8=True``
|
if ``decode_data=True`` was given as an init argument, otherwise
|
||||||
was given as an init parameter, in which case it contains the following
|
it contains the following keys:
|
||||||
keys:
|
|
||||||
|
|
||||||
*mail_options*:
|
*mail_options*:
|
||||||
a list of all received parameters to the ``MAIL``
|
a list of all received parameters to the ``MAIL``
|
||||||
|
@ -108,10 +106,13 @@ SMTPServer Objects
|
||||||
*localaddr* and *remoteaddr* may now contain IPv6 addresses.
|
*localaddr* and *remoteaddr* may now contain IPv6 addresses.
|
||||||
|
|
||||||
.. versionadded:: 3.5
|
.. versionadded:: 3.5
|
||||||
the *decode_data* and *enable_SMTPUTF8* constructor arguments, and the
|
The *decode_data* and *enable_SMTPUTF8* constructor parameters, and the
|
||||||
*kwargs* argument to :meth:`process_message` when one or more of these is
|
*kwargs* parameter to :meth:`process_message` when one or more of these is
|
||||||
specified.
|
specified.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.6
|
||||||
|
*decode_data* is now ``False`` by default.
|
||||||
|
|
||||||
|
|
||||||
DebuggingServer Objects
|
DebuggingServer Objects
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -150,7 +151,7 @@ SMTPChannel Objects
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
.. class:: SMTPChannel(server, conn, addr, data_size_limit=33554432,\
|
.. class:: SMTPChannel(server, conn, addr, data_size_limit=33554432,\
|
||||||
map=None, enable_SMTPUTF8=False, decode_data=True)
|
map=None, enable_SMTPUTF8=False, decode_data=False)
|
||||||
|
|
||||||
Create a new :class:`SMTPChannel` object which manages the communication
|
Create a new :class:`SMTPChannel` object which manages the communication
|
||||||
between the server and a single SMTP client.
|
between the server and a single SMTP client.
|
||||||
|
@ -162,22 +163,25 @@ SMTPChannel Objects
|
||||||
limit.
|
limit.
|
||||||
|
|
||||||
*enable_SMTPUTF8* determins whether the ``SMTPUTF8`` extension (as defined
|
*enable_SMTPUTF8* determins whether the ``SMTPUTF8`` extension (as defined
|
||||||
in :RFC:`6531`) should be enabled. The default is ``False``. A
|
in :RFC:`6531`) should be enabled. The default is ``False``.
|
||||||
:exc:`ValueError` is raised if both *enable_SMTPUTF8* and *decode_data* are
|
*decode_data* and *enable_SMTPUTF8* cannot be set to ``True`` at the same
|
||||||
set to ``True`` at the same time.
|
time.
|
||||||
|
|
||||||
A dictionary can be specified in *map* to avoid using a global socket map.
|
A dictionary can be specified in *map* to avoid using a global socket map.
|
||||||
|
|
||||||
*decode_data* specifies whether the data portion of the SMTP transaction
|
*decode_data* specifies whether the data portion of the SMTP transaction
|
||||||
should be decoded using UTF-8. The default is ``True`` for backward
|
should be decoded using UTF-8. The default is ``False``.
|
||||||
compatibility reasons, but will change to ``False`` in Python 3.6. Specify
|
*decode_data* and *enable_SMTPUTF8* cannot be set to ``True`` at the same
|
||||||
the keyword value explicitly to avoid the :exc:`DeprecationWarning`.
|
time.
|
||||||
|
|
||||||
To use a custom SMTPChannel implementation you need to override the
|
To use a custom SMTPChannel implementation you need to override the
|
||||||
:attr:`SMTPServer.channel_class` of your :class:`SMTPServer`.
|
:attr:`SMTPServer.channel_class` of your :class:`SMTPServer`.
|
||||||
|
|
||||||
.. versionchanged:: 3.5
|
.. versionchanged:: 3.5
|
||||||
the *decode_data* and *enable_SMTPUTF8* arguments were added.
|
The *decode_data* and *enable_SMTPUTF8* parameters were added.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.6
|
||||||
|
*decode_data* is now ``False`` by default.
|
||||||
|
|
||||||
The :class:`SMTPChannel` has the following instance variables:
|
The :class:`SMTPChannel` has the following instance variables:
|
||||||
|
|
||||||
|
|
45
Lib/smtpd.py
45
Lib/smtpd.py
|
@ -128,24 +128,17 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
return self.command_size_limit
|
return self.command_size_limit
|
||||||
|
|
||||||
def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
|
def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
|
||||||
map=None, enable_SMTPUTF8=False, decode_data=None):
|
map=None, enable_SMTPUTF8=False, decode_data=False):
|
||||||
asynchat.async_chat.__init__(self, conn, map=map)
|
asynchat.async_chat.__init__(self, conn, map=map)
|
||||||
self.smtp_server = server
|
self.smtp_server = server
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.data_size_limit = data_size_limit
|
self.data_size_limit = data_size_limit
|
||||||
self.enable_SMTPUTF8 = enable_SMTPUTF8
|
self.enable_SMTPUTF8 = enable_SMTPUTF8 = bool(enable_SMTPUTF8)
|
||||||
if enable_SMTPUTF8:
|
self._decode_data = decode_data = bool(decode_data)
|
||||||
if decode_data:
|
if enable_SMTPUTF8 and decode_data:
|
||||||
raise ValueError("decode_data and enable_SMTPUTF8 cannot"
|
raise ValueError("decode_data and enable_SMTPUTF8 cannot"
|
||||||
" be set to True at the same time")
|
" be set to True at the same time")
|
||||||
decode_data = False
|
|
||||||
if decode_data is None:
|
|
||||||
warn("The decode_data default of True will change to False in 3.6;"
|
|
||||||
" specify an explicit value for this keyword",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
decode_data = True
|
|
||||||
self._decode_data = decode_data
|
|
||||||
if decode_data:
|
if decode_data:
|
||||||
self._emptystring = ''
|
self._emptystring = ''
|
||||||
self._linesep = '\r\n'
|
self._linesep = '\r\n'
|
||||||
|
@ -635,23 +628,15 @@ class SMTPServer(asyncore.dispatcher):
|
||||||
|
|
||||||
def __init__(self, localaddr, remoteaddr,
|
def __init__(self, localaddr, remoteaddr,
|
||||||
data_size_limit=DATA_SIZE_DEFAULT, map=None,
|
data_size_limit=DATA_SIZE_DEFAULT, map=None,
|
||||||
enable_SMTPUTF8=False, decode_data=None):
|
enable_SMTPUTF8=False, decode_data=False):
|
||||||
self._localaddr = localaddr
|
self._localaddr = localaddr
|
||||||
self._remoteaddr = remoteaddr
|
self._remoteaddr = remoteaddr
|
||||||
self.data_size_limit = data_size_limit
|
self.data_size_limit = data_size_limit
|
||||||
self.enable_SMTPUTF8 = enable_SMTPUTF8
|
self.enable_SMTPUTF8 = enable_SMTPUTF8 = bool(enable_SMTPUTF8)
|
||||||
if enable_SMTPUTF8:
|
self._decode_data = decode_data = bool(decode_data)
|
||||||
if decode_data:
|
if enable_SMTPUTF8 and decode_data:
|
||||||
raise ValueError("The decode_data and enable_SMTPUTF8"
|
raise ValueError("decode_data and enable_SMTPUTF8 cannot"
|
||||||
" parameters cannot be set to True at the"
|
" be set to True at the same time")
|
||||||
" same time.")
|
|
||||||
decode_data = False
|
|
||||||
if decode_data is None:
|
|
||||||
warn("The decode_data default of True will change to False in 3.6;"
|
|
||||||
" specify an explicit value for this keyword",
|
|
||||||
DeprecationWarning, 2)
|
|
||||||
decode_data = True
|
|
||||||
self._decode_data = decode_data
|
|
||||||
asyncore.dispatcher.__init__(self, map=map)
|
asyncore.dispatcher.__init__(self, map=map)
|
||||||
try:
|
try:
|
||||||
gai_results = socket.getaddrinfo(*localaddr,
|
gai_results = socket.getaddrinfo(*localaddr,
|
||||||
|
@ -698,9 +683,9 @@ class SMTPServer(asyncore.dispatcher):
|
||||||
containing a `.' followed by other text has had the leading dot
|
containing a `.' followed by other text has had the leading dot
|
||||||
removed.
|
removed.
|
||||||
|
|
||||||
kwargs is a dictionary containing additional information. It is empty
|
kwargs is a dictionary containing additional information. It is
|
||||||
unless decode_data=False or enable_SMTPUTF8=True was given as init
|
empty if decode_data=True was given as init parameter, otherwise
|
||||||
parameter, in which case ut will contain the following keys:
|
it will contain the following keys:
|
||||||
'mail_options': list of parameters to the mail command. All
|
'mail_options': list of parameters to the mail command. All
|
||||||
elements are uppercase strings. Example:
|
elements are uppercase strings. Example:
|
||||||
['BODY=8BITMIME', 'SMTPUTF8'].
|
['BODY=8BITMIME', 'SMTPUTF8'].
|
||||||
|
|
|
@ -53,10 +53,6 @@ class SMTPDServerTest(unittest.TestCase):
|
||||||
write_line(b'DATA')
|
write_line(b'DATA')
|
||||||
self.assertRaises(NotImplementedError, write_line, b'spam\r\n.\r\n')
|
self.assertRaises(NotImplementedError, write_line, b'spam\r\n.\r\n')
|
||||||
|
|
||||||
def test_decode_data_default_warns(self):
|
|
||||||
with self.assertWarns(DeprecationWarning):
|
|
||||||
smtpd.SMTPServer((support.HOST, 0), ('b', 0))
|
|
||||||
|
|
||||||
def test_decode_data_and_enable_SMTPUTF8_raises(self):
|
def test_decode_data_and_enable_SMTPUTF8_raises(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
ValueError,
|
ValueError,
|
||||||
|
@ -108,10 +104,9 @@ class DebuggingServerTest(unittest.TestCase):
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
def test_process_message_with_decode_data_false(self):
|
def test_process_message_with_decode_data_false(self):
|
||||||
server = smtpd.DebuggingServer((support.HOST, 0), ('b', 0),
|
server = smtpd.DebuggingServer((support.HOST, 0), ('b', 0))
|
||||||
decode_data=False)
|
|
||||||
conn, addr = server.accept()
|
conn, addr = server.accept()
|
||||||
channel = smtpd.SMTPChannel(server, conn, addr, decode_data=False)
|
channel = smtpd.SMTPChannel(server, conn, addr)
|
||||||
with support.captured_stdout() as s:
|
with support.captured_stdout() as s:
|
||||||
self.send_data(channel, b'From: test\n\nh\xc3\xa9llo\xff\n')
|
self.send_data(channel, b'From: test\n\nh\xc3\xa9llo\xff\n')
|
||||||
stdout = s.getvalue()
|
stdout = s.getvalue()
|
||||||
|
@ -175,13 +170,11 @@ class TestFamilyDetection(unittest.TestCase):
|
||||||
|
|
||||||
@unittest.skipUnless(support.IPV6_ENABLED, "IPv6 not enabled")
|
@unittest.skipUnless(support.IPV6_ENABLED, "IPv6 not enabled")
|
||||||
def test_socket_uses_IPv6(self):
|
def test_socket_uses_IPv6(self):
|
||||||
server = smtpd.SMTPServer((support.HOSTv6, 0), (support.HOST, 0),
|
server = smtpd.SMTPServer((support.HOSTv6, 0), (support.HOST, 0))
|
||||||
decode_data=False)
|
|
||||||
self.assertEqual(server.socket.family, socket.AF_INET6)
|
self.assertEqual(server.socket.family, socket.AF_INET6)
|
||||||
|
|
||||||
def test_socket_uses_IPv4(self):
|
def test_socket_uses_IPv4(self):
|
||||||
server = smtpd.SMTPServer((support.HOST, 0), (support.HOSTv6, 0),
|
server = smtpd.SMTPServer((support.HOST, 0), (support.HOSTv6, 0))
|
||||||
decode_data=False)
|
|
||||||
self.assertEqual(server.socket.family, socket.AF_INET)
|
self.assertEqual(server.socket.family, socket.AF_INET)
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,18 +197,18 @@ class TestRcptOptionParsing(unittest.TestCase):
|
||||||
channel.handle_read()
|
channel.handle_read()
|
||||||
|
|
||||||
def test_params_rejected(self):
|
def test_params_rejected(self):
|
||||||
server = DummyServer((support.HOST, 0), ('b', 0), decode_data=False)
|
server = DummyServer((support.HOST, 0), ('b', 0))
|
||||||
conn, addr = server.accept()
|
conn, addr = server.accept()
|
||||||
channel = smtpd.SMTPChannel(server, conn, addr, decode_data=False)
|
channel = smtpd.SMTPChannel(server, conn, addr)
|
||||||
self.write_line(channel, b'EHLO example')
|
self.write_line(channel, b'EHLO example')
|
||||||
self.write_line(channel, b'MAIL from: <foo@example.com> size=20')
|
self.write_line(channel, b'MAIL from: <foo@example.com> size=20')
|
||||||
self.write_line(channel, b'RCPT to: <foo@example.com> foo=bar')
|
self.write_line(channel, b'RCPT to: <foo@example.com> foo=bar')
|
||||||
self.assertEqual(channel.socket.last, self.error_response)
|
self.assertEqual(channel.socket.last, self.error_response)
|
||||||
|
|
||||||
def test_nothing_accepted(self):
|
def test_nothing_accepted(self):
|
||||||
server = DummyServer((support.HOST, 0), ('b', 0), decode_data=False)
|
server = DummyServer((support.HOST, 0), ('b', 0))
|
||||||
conn, addr = server.accept()
|
conn, addr = server.accept()
|
||||||
channel = smtpd.SMTPChannel(server, conn, addr, decode_data=False)
|
channel = smtpd.SMTPChannel(server, conn, addr)
|
||||||
self.write_line(channel, b'EHLO example')
|
self.write_line(channel, b'EHLO example')
|
||||||
self.write_line(channel, b'MAIL from: <foo@example.com> size=20')
|
self.write_line(channel, b'MAIL from: <foo@example.com> size=20')
|
||||||
self.write_line(channel, b'RCPT to: <foo@example.com>')
|
self.write_line(channel, b'RCPT to: <foo@example.com>')
|
||||||
|
@ -257,9 +250,9 @@ class TestMailOptionParsing(unittest.TestCase):
|
||||||
self.assertEqual(channel.socket.last, b'250 OK\r\n')
|
self.assertEqual(channel.socket.last, b'250 OK\r\n')
|
||||||
|
|
||||||
def test_with_decode_data_false(self):
|
def test_with_decode_data_false(self):
|
||||||
server = DummyServer((support.HOST, 0), ('b', 0), decode_data=False)
|
server = DummyServer((support.HOST, 0), ('b', 0))
|
||||||
conn, addr = server.accept()
|
conn, addr = server.accept()
|
||||||
channel = smtpd.SMTPChannel(server, conn, addr, decode_data=False)
|
channel = smtpd.SMTPChannel(server, conn, addr)
|
||||||
self.write_line(channel, b'EHLO example')
|
self.write_line(channel, b'EHLO example')
|
||||||
for line in [
|
for line in [
|
||||||
b'MAIL from: <foo@example.com> size=20 SMTPUTF8',
|
b'MAIL from: <foo@example.com> size=20 SMTPUTF8',
|
||||||
|
@ -765,13 +758,6 @@ class SMTPDChannelTest(unittest.TestCase):
|
||||||
with support.check_warnings(('', DeprecationWarning)):
|
with support.check_warnings(('', DeprecationWarning)):
|
||||||
self.channel._SMTPChannel__addr = 'spam'
|
self.channel._SMTPChannel__addr = 'spam'
|
||||||
|
|
||||||
def test_decode_data_default_warning(self):
|
|
||||||
with self.assertWarns(DeprecationWarning):
|
|
||||||
server = DummyServer((support.HOST, 0), ('b', 0))
|
|
||||||
conn, addr = self.server.accept()
|
|
||||||
with self.assertWarns(DeprecationWarning):
|
|
||||||
smtpd.SMTPChannel(server, conn, addr)
|
|
||||||
|
|
||||||
@unittest.skipUnless(support.IPV6_ENABLED, "IPv6 not enabled")
|
@unittest.skipUnless(support.IPV6_ENABLED, "IPv6 not enabled")
|
||||||
class SMTPDChannelIPv6Test(SMTPDChannelTest):
|
class SMTPDChannelIPv6Test(SMTPDChannelTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -845,12 +831,9 @@ class SMTPDChannelWithDecodeDataFalse(unittest.TestCase):
|
||||||
smtpd.socket = asyncore.socket = mock_socket
|
smtpd.socket = asyncore.socket = mock_socket
|
||||||
self.old_debugstream = smtpd.DEBUGSTREAM
|
self.old_debugstream = smtpd.DEBUGSTREAM
|
||||||
self.debug = smtpd.DEBUGSTREAM = io.StringIO()
|
self.debug = smtpd.DEBUGSTREAM = io.StringIO()
|
||||||
self.server = DummyServer((support.HOST, 0), ('b', 0),
|
self.server = DummyServer((support.HOST, 0), ('b', 0))
|
||||||
decode_data=False)
|
|
||||||
conn, addr = self.server.accept()
|
conn, addr = self.server.accept()
|
||||||
# Set decode_data to False
|
self.channel = smtpd.SMTPChannel(self.server, conn, addr)
|
||||||
self.channel = smtpd.SMTPChannel(self.server, conn, addr,
|
|
||||||
decode_data=False)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
asyncore.close_all()
|
asyncore.close_all()
|
||||||
|
|
|
@ -277,6 +277,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #27033: The default value of the decode_data parameter for
|
||||||
|
smtpd.SMTPChannel and smtpd.SMTPServer constructors is changed to False.
|
||||||
|
|
||||||
- Issue #27034: Removed deprecated class asynchat.fifo.
|
- Issue #27034: Removed deprecated class asynchat.fifo.
|
||||||
|
|
||||||
- Issue #26870: Added readline.set_auto_history(), which can stop entries
|
- Issue #26870: Added readline.set_auto_history(), which can stop entries
|
||||||
|
|
Loading…
Reference in New Issue