diff options
-rwxr-xr-x | Lib/smtplib.py | 18 | ||||
-rw-r--r-- | Lib/test/test_smtplib.py | 13 | ||||
-rw-r--r-- | Misc/NEWS | 5 |
3 files changed, 33 insertions, 3 deletions
diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 66b5879..ec43666 100755 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -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 diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 3f7b9b4..16e90f4 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -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) @@ -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. |