summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xLib/smtplib.py18
-rw-r--r--Lib/test/test_smtplib.py13
-rw-r--r--Misc/NEWS5
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)
diff --git a/Misc/NEWS b/Misc/NEWS
index 52cb825..8519b61 100644
--- 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.