[Python-checkins] cpython: Closes #11959: SMTPServer and SMTPChannel now take an optional map, use of

vinay.sajip python-checkins at python.org
Fri Jun 7 16:21:55 CEST 2013


http://hg.python.org/cpython/rev/ed498f477549
changeset:   84047:ed498f477549
user:        Vinay Sajip <vinay_sajip at yahoo.co.uk>
date:        Fri Jun 07 15:21:41 2013 +0100
summary:
  Closes #11959: SMTPServer and SMTPChannel now take an optional map, use of which avoids affecting global state.

files:
  Doc/library/smtpd.rst    |  19 +++++++-
  Lib/smtpd.py             |  12 +++--
  Lib/test/test_logging.py |  64 +--------------------------
  Misc/NEWS                |   3 +
  4 files changed, 30 insertions(+), 68 deletions(-)


diff --git a/Doc/library/smtpd.rst b/Doc/library/smtpd.rst
--- a/Doc/library/smtpd.rst
+++ b/Doc/library/smtpd.rst
@@ -27,7 +27,8 @@
 ------------------
 
 
-.. 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
    *localaddr*.  It will treat *remoteaddr* as an upstream SMTP relayer.  It
@@ -38,6 +39,8 @@
    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.
+
    .. method:: process_message(peer, mailfrom, rcpttos, data)
 
       Raise :exc:`NotImplementedError` exception. Override this in subclasses to
@@ -53,6 +56,9 @@
       Override this in subclasses to use a custom :class:`SMTPChannel` for
       managing SMTP clients.
 
+   .. versionchanged:: 3.4
+      The *map* argument was added.
+
 
 DebuggingServer Objects
 -----------------------
@@ -90,11 +96,20 @@
 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
    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
    :attr:`SMTPServer.channel_class` of your :class:`SMTPServer`.
 
diff --git a/Lib/smtpd.py b/Lib/smtpd.py
--- a/Lib/smtpd.py
+++ b/Lib/smtpd.py
@@ -121,8 +121,9 @@
         })
     max_command_size_limit = max(command_size_limits.values())
 
-    def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT):
-        asynchat.async_chat.__init__(self, conn)
+    def __init__(self, server, conn, addr, data_size_limit=DATA_SIZE_DEFAULT,
+                 map=None):
+        asynchat.async_chat.__init__(self, conn, map=map)
         self.smtp_server = server
         self.conn = conn
         self.addr = addr
@@ -576,11 +577,11 @@
     channel_class = SMTPChannel
 
     def __init__(self, localaddr, remoteaddr,
-                 data_size_limit=DATA_SIZE_DEFAULT):
+                 data_size_limit=DATA_SIZE_DEFAULT, map=None):
         self._localaddr = localaddr
         self._remoteaddr = remoteaddr
         self.data_size_limit = data_size_limit
-        asyncore.dispatcher.__init__(self)
+        asyncore.dispatcher.__init__(self, map=map)
         try:
             self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
             # try to re-use a server port if possible
@@ -597,7 +598,8 @@
 
     def handle_accepted(self, conn, addr):
         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"
     def process_message(self, peer, mailfrom, rcpttos, data):
diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py
--- a/Lib/test/test_logging.py
+++ b/Lib/test/test_logging.py
@@ -659,41 +659,6 @@
 # -- if it proves to be of wider utility than just test_logging
 
 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):
         """
         This class implements a test SMTP server.
@@ -714,37 +679,14 @@
                         :func:`asyncore.loop`. This avoids changing the
                         :mod:`asyncore` module's global state.
         """
-        channel_class = TestSMTPChannel
 
         def __init__(self, addr, handler, poll_interval, sockmap):
-            self._localaddr = addr
-            self._remoteaddr = None
-            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
+            smtpd.SMTPServer.__init__(self, addr, None, map=sockmap)
+            self.port = self.socket.getsockname()[1]
             self._handler = handler
             self._thread = None
             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):
             """
             Delegates to the handler passed in to the server's constructor.
@@ -775,7 +717,7 @@
                                   :func:`asyncore.loop`.
             """
             try:
-                asyncore.loop(poll_interval, map=self.sockmap)
+                asyncore.loop(poll_interval, map=self._map)
             except OSError:
                 # On FreeBSD 8, closing the server repeatably
                 # raises this error. We swallow it if the
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -112,6 +112,9 @@
 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
   socket.gethostname() now decodes the hostname from the locale encoding,
   instead of using the UTF-8 encoding in strict mode.

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list