mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
Closes #11959: SMTPServer and SMTPChannel now take an optional map, use of which avoids affecting global state.
This commit is contained in:
parent
4e694d6fa9
commit
30298b468b
4 changed files with 30 additions and 68 deletions
|
@ -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
|
||||||
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
|
||||||
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
|
||||||
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`.
|
||||||
|
|
||||||
|
|
12
Lib/smtpd.py
12
Lib/smtpd.py
|
@ -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):
|
||||||
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):
|
||||||
|
|
||||||
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):
|
||||||
# -- 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:
|
||||||
: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:
|
||||||
: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
|
||||||
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.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue