changeset: 90303:842014ab1c06 parent: 90300:528234542ff0 parent: 90302:3c441e9ccf87 user: R David Murray date: Mon Apr 14 18:22:00 2014 -0400 files: Misc/NEWS description: Merge #17498: Defer SMTPServerDisconnected errors until the next command. diff -r 528234542ff0 -r 842014ab1c06 Lib/smtplib.py --- a/Lib/smtplib.py Mon Apr 14 16:49:07 2014 -0400 +++ b/Lib/smtplib.py Mon Apr 14 18:22:00 2014 -0400 @@ -478,6 +478,18 @@ """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 @@ 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 @@ 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 -r 528234542ff0 -r 842014ab1c06 Lib/test/test_smtplib.py --- a/Lib/test/test_smtplib.py Mon Apr 14 16:49:07 2014 -0400 +++ b/Lib/test/test_smtplib.py Mon Apr 14 18:22:00 2014 -0400 @@ -619,6 +619,7 @@ 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 @@ 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 @@ #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 -r 528234542ff0 -r 842014ab1c06 Misc/NEWS --- a/Misc/NEWS Mon Apr 14 16:49:07 2014 -0400 +++ b/Misc/NEWS Mon Apr 14 18:22:00 2014 -0400 @@ -46,6 +46,11 @@ 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.