]> granicus.if.org Git - python/commitdiff
#17498: Defer SMTPServerDisconnected errors until the next command.
authorR David Murray <rdmurray@bitdance.com>
Mon, 14 Apr 2014 22:21:38 +0000 (18:21 -0400)
committerR David Murray <rdmurray@bitdance.com>
Mon, 14 Apr 2014 22:21:38 +0000 (18:21 -0400)
Normally an SMTP server will return an error, and smtplib will then issue an
RSET to return the connection to the known starting state.  Some servers,
however, disconnect after issuing certain errors.  When we issue the RSET,
this would result in raising an SMTPServerDisconnected error, *instead* of
returning the error code the user of the library was expecting.  This fix
makes the internal RSET calls ignore the disconnection so that the error code
is returned.  The user of the library will then get the SMTPServerDisconnected
error the next time they try to talk to the server.

Patch by Kushal Das.

Lib/smtplib.py
Lib/test/test_smtplib.py
Misc/NEWS

index 66b5879defdca1f3dec0839db6211d8d6541d313..ec43666df3db2fd0a268180a49167a9ed4ad0a07 100755 (executable)
@@ -478,6 +478,18 @@ class SMTP:
         """SMTP 'rset' command -- resets session."""
         return self.docmd("rset")
 
+    def _rset(self):
+        """Internal 'rset' command which ignores any SMTPServerDisconnected error.
+
+        Used internally in the library, since the server disconnected error
+        should appear to the application when the *next* command is issued, if
+        we are doing an internal "safety" reset.
+        """
+        try:
+            self.rset()
+        except SMTPServerDisconnected:
+            pass
+
     def noop(self):
         """SMTP 'noop' command -- doesn't do anything :>"""
         return self.docmd("noop")
@@ -762,7 +774,7 @@ class SMTP:
             if code == 421:
                 self.close()
             else:
-                self.rset()
+                self._rset()
             raise SMTPSenderRefused(code, resp, from_addr)
         senderrs = {}
         if isinstance(to_addrs, str):
@@ -776,14 +788,14 @@ class SMTP:
                 raise SMTPRecipientsRefused(senderrs)
         if len(senderrs) == len(to_addrs):
             # the server refused all our recipients
-            self.rset()
+            self._rset()
             raise SMTPRecipientsRefused(senderrs)
         (code, resp) = self.data(msg)
         if code != 250:
             if code == 421:
                 self.close()
             else:
-                self.rset()
+                self._rset()
             raise SMTPDataError(code, resp)
         #if we got here then somebody got our mail
         return senderrs
index 3f7b9b4175cc1deb6d3742aa5025b7ab3ac69b79..16e90f4ab321ec6afcfaf8ff215d7cf46b0471c8 100644 (file)
@@ -619,6 +619,7 @@ class SimSMTPChannel(smtpd.SMTPChannel):
     data_response = None
     rcpt_count = 0
     rset_count = 0
+    disconnect = 0
 
     def __init__(self, extra_features, *args, **kw):
         self._extrafeatures = ''.join(
@@ -684,6 +685,8 @@ class SimSMTPChannel(smtpd.SMTPChannel):
             super().smtp_MAIL(arg)
         else:
             self.push(self.mail_response)
+            if self.disconnect:
+                self.close_when_done()
 
     def smtp_RCPT(self, arg):
         if self.rcpt_response is None:
@@ -875,6 +878,16 @@ class SMTPSimTests(unittest.TestCase):
     #TODO: add tests for correct AUTH method fallback now that the
     #test infrastructure can support it.
 
+    # Issue 17498: make sure _rset does not raise SMTPServerDisconnected exception
+    def test__rest_from_mail_cmd(self):
+        smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
+        smtp.noop()
+        self.serv._SMTPchannel.mail_response = '451 Requested action aborted'
+        self.serv._SMTPchannel.disconnect = True
+        with self.assertRaises(smtplib.SMTPSenderRefused):
+            smtp.sendmail('John', 'Sally', 'test message')
+        self.assertIsNone(smtp.sock)
+
     # Issue 5713: make sure close, not rset, is called if we get a 421 error
     def test_421_from_mail_cmd(self):
         smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15)
index 52cb82594ec3df967a93427082725f5b9fd85c1f..8519b6196ce6ab0f66513a1edeb605f4f27a203b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -37,6 +37,11 @@ Core and Builtins
 Library
 -------
 
+- Issue #17498: Some SMTP servers disconnect after certain errors, violating
+  strict RFC conformance.  Instead of losing the error code when we issue the
+  subsequent RSET, smtplib now returns the error code and defers raising the
+  SMTPServerDisconnected error until the next command is issued.
+
 - Issue #17826: setting an iterable side_effect on a mock function created by
   create_autospec now works. Patch by Kushal Das.