Commit 4e65c6e4 authored by Vinay Sajip's avatar Vinay Sajip

Closes #11959: SMTPServer and SMTPChannel now take an optional map, use of...

Closes #11959: SMTPServer and SMTPChannel now take an optional map, use of which avoids affecting global state.
parent ae20a5d9
...@@ -27,7 +27,8 @@ SMTPServer Objects ...@@ -27,7 +27,8 @@ SMTPServer Objects
------------------ ------------------
.. class:: SMTPServer(localaddr, remoteaddr, data_size_limit=33554432) .. class:: SMTPServer(localaddr, remoteaddr, data_size_limit=33554432,
map=None)
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
...@@ -38,6 +39,8 @@ SMTPServer Objects ...@@ -38,6 +39,8 @@ SMTPServer Objects
accepted in a ``DATA`` command. A value of ``None`` or ``0`` means no accepted in a ``DATA`` command. A value of ``None`` or ``0`` means no
limit. limit.
A dictionary can be specified in *map* to avoid using a global socket map.
.. method:: process_message(peer, mailfrom, rcpttos, data) .. method:: process_message(peer, mailfrom, rcpttos, data)
Raise :exc:`NotImplementedError` exception. Override this in subclasses to Raise :exc:`NotImplementedError` exception. Override this in subclasses to
...@@ -53,6 +56,9 @@ SMTPServer Objects ...@@ -53,6 +56,9 @@ SMTPServer Objects
Override this in subclasses to use a custom :class:`SMTPChannel` for Override this in subclasses to use a custom :class:`SMTPChannel` for
managing SMTP clients. managing SMTP clients.
.. versionchanged:: 3.4
The *map* argument was added.
DebuggingServer Objects DebuggingServer Objects
----------------------- -----------------------
...@@ -90,11 +96,20 @@ MailmanProxy Objects ...@@ -90,11 +96,20 @@ MailmanProxy Objects
SMTPChannel Objects SMTPChannel Objects
------------------- -------------------
.. class:: SMTPChannel(server, conn, addr) .. class:: SMTPChannel(server, conn, addr, data_size_limit=33554432,
map=None))
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.
*conn* and *addr* are as per the instance variables described below.
*data_size_limit* specifies the maximum number of bytes that will be
accepted in a ``DATA`` command. A value of ``None`` or ``0`` means no
limit.
A dictionary can be specified in *map* to avoid using a global socket map.
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`.
......
...@@ -121,8 +121,9 @@ class SMTPChannel(asynchat.async_chat): ...@@ -121,8 +121,9 @@ class SMTPChannel(asynchat.async_chat):
}) })
max_command_size_limit = max(command_size_limits.values()) max_command_size_limit = max(command_size_limits.values())
def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT): def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
asynchat.async_chat.__init__(self, conn) map=None):
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
...@@ -576,11 +577,11 @@ class SMTPServer(asyncore.dispatcher): ...@@ -576,11 +577,11 @@ class SMTPServer(asyncore.dispatcher):
channel_class = SMTPChannel channel_class = SMTPChannel
def __init__(self, localaddr, remoteaddr, def __init__(self, localaddr, remoteaddr,
data_size_limit=DATA_SIZE_DEFAULT): data_size_limit=DATA_SIZE_DEFAULT, map=None):
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
asyncore.dispatcher.__init__(self) asyncore.dispatcher.__init__(self, map=map)
try: try:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
# try to re-use a server port if possible # try to re-use a server port if possible
...@@ -597,7 +598,8 @@ class SMTPServer(asyncore.dispatcher): ...@@ -597,7 +598,8 @@ class SMTPServer(asyncore.dispatcher):
def handle_accepted(self, conn, addr): def handle_accepted(self, conn, addr):
print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM) print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
channel = self.channel_class(self, conn, addr, self.data_size_limit) channel = self.channel_class(self, conn, addr, self.data_size_limit,
self._map)
# API for "doing something useful with the message" # API for "doing something useful with the message"
def process_message(self, peer, mailfrom, rcpttos, data): def process_message(self, peer, mailfrom, rcpttos, data):
......
...@@ -659,41 +659,6 @@ class StreamHandlerTest(BaseTest): ...@@ -659,41 +659,6 @@ class StreamHandlerTest(BaseTest):
# -- if it proves to be of wider utility than just test_logging # -- if it proves to be of wider utility than just test_logging
if threading: if threading:
class TestSMTPChannel(smtpd.SMTPChannel):
"""
This derived class has had to be created because smtpd does not
support use of custom channel maps, although they are allowed by
asyncore's design. Issue #11959 has been raised to address this,
and if resolved satisfactorily, some of this code can be removed.
"""
def __init__(self, server, conn, addr, sockmap):
asynchat.async_chat.__init__(self, conn, sockmap)
self.smtp_server = server
self.conn = conn
self.addr = addr
self.data_size_limit = None
self.received_lines = []
self.smtp_state = self.COMMAND
self.seen_greeting = ''
self.mailfrom = None
self.rcpttos = []
self.received_data = ''
self.fqdn = socket.getfqdn()
self.num_bytes = 0
try:
self.peer = conn.getpeername()
except OSError as err:
# a race condition may occur if the other end is closing
# before we can get the peername
self.close()
if err.args[0] != errno.ENOTCONN:
raise
return
self.push('220 %s %s' % (self.fqdn, smtpd.__version__))
self.set_terminator(b'\r\n')
self.extended_smtp = False
class TestSMTPServer(smtpd.SMTPServer): class TestSMTPServer(smtpd.SMTPServer):
""" """
This class implements a test SMTP server. This class implements a test SMTP server.
...@@ -714,37 +679,14 @@ if threading: ...@@ -714,37 +679,14 @@ if threading:
:func:`asyncore.loop`. This avoids changing the :func:`asyncore.loop`. This avoids changing the
:mod:`asyncore` module's global state. :mod:`asyncore` module's global state.
""" """
channel_class = TestSMTPChannel
def __init__(self, addr, handler, poll_interval, sockmap): def __init__(self, addr, handler, poll_interval, sockmap):
self._localaddr = addr smtpd.SMTPServer.__init__(self, addr, None, map=sockmap)
self._remoteaddr = None self.port = self.socket.getsockname()[1]
self.data_size_limit = None
self.sockmap = sockmap
asyncore.dispatcher.__init__(self, map=sockmap)
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(0)
self.set_socket(sock, map=sockmap)
# try to re-use a server port if possible
self.set_reuse_addr()
self.bind(addr)
self.port = sock.getsockname()[1]
self.listen(5)
except:
self.close()
raise
self._handler = handler self._handler = handler
self._thread = None self._thread = None
self.poll_interval = poll_interval self.poll_interval = poll_interval
def handle_accepted(self, conn, addr):
"""
Redefined only because the base class does not pass in a
map, forcing use of a global in :mod:`asyncore`.
"""
channel = self.channel_class(self, conn, addr, self.sockmap)
def process_message(self, peer, mailfrom, rcpttos, data): def process_message(self, peer, mailfrom, rcpttos, data):
""" """
Delegates to the handler passed in to the server's constructor. Delegates to the handler passed in to the server's constructor.
...@@ -775,7 +717,7 @@ if threading: ...@@ -775,7 +717,7 @@ if threading:
:func:`asyncore.loop`. :func:`asyncore.loop`.
""" """
try: try:
asyncore.loop(poll_interval, map=self.sockmap) asyncore.loop(poll_interval, map=self._map)
except OSError: except OSError:
# On FreeBSD 8, closing the server repeatably # On FreeBSD 8, closing the server repeatably
# raises this error. We swallow it if the # raises this error. We swallow it if the
......
...@@ -112,6 +112,9 @@ Core and Builtins ...@@ -112,6 +112,9 @@ Core and Builtins
Library Library
------- -------
- Issue #11959: SMTPServer and SMTPChannel now take an optional map, use of
which avoids affecting global state.
- Issue #18109: os.uname() now decodes fields from the locale encoding, and - Issue #18109: os.uname() now decodes fields from the locale encoding, and
socket.gethostname() now decodes the hostname from the locale encoding, socket.gethostname() now decodes the hostname from the locale encoding,
instead of using the UTF-8 encoding in strict mode. instead of using the UTF-8 encoding in strict mode.
......
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