# Simulated SMTP channel & server
class SimSMTPChannel(smtpd.SMTPChannel):
- # For testing failures in QUIT when using the context manager API.
+ quit_response = None
+ mail_response = None
+ rcpt_response = None
+ data_response = None
+ rcpt_count = 0
+ rset_count = 0
def __init__(self, extra_features, *args, **kw):
self._extrafeatures = ''.join(
'250-DELIVERBY\r\n')
resp = resp + self._extrafeatures + '250 HELP'
self.push(resp)
++ self.seen_greeting = arg
++ self.extended_smtp = True
def smtp_VRFY(self, arg):
# For max compatibility smtplib should be sending the raw address.
else:
self.push('550 No access for you!')
- # args is ignored
+ def smtp_QUIT(self, arg):
+ if self.quit_response is None:
+ super(SimSMTPChannel, self).smtp_QUIT(arg)
+ else:
+ self.push(self.quit_response)
+ self.close_when_done()
+
+ def smtp_MAIL(self, arg):
+ if self.mail_response is None:
+ super().smtp_MAIL(arg)
+ else:
+ self.push(self.mail_response)
+
+ def smtp_RCPT(self, arg):
+ if self.rcpt_response is None:
+ super().smtp_RCPT(arg)
+ return
+ self.push(self.rcpt_response[self.rcpt_count])
+ self.rcpt_count += 1
+
+ def smtp_RSET(self, arg):
+ super().smtp_RSET(arg)
+ self.rset_count += 1
+
+ def smtp_DATA(self, arg):
+ if self.data_response is None:
+ super().smtp_DATA(arg)
+ else:
+ self.push(self.data_response)
+
def handle_error(self):
raise
smtpd.SMTPServer.__init__(self, *args, **kw)
def handle_accepted(self, conn, addr):
- self._SMTPchannel = SimSMTPChannel(
- self._SMTPchannel = self.channel_class(self._extra_features,
- self, conn, addr)
++ self._SMTPchannel = self.channel_class(
+ self._extra_features, self, conn, addr)
- self._SMTPchannel.quit_response = self.quit_response
def process_message(self, peer, mailfrom, rcpttos, data):
pass
self.assertIn(sim_auth_credentials['cram-md5'], str(err))
smtp.close()
- self.serv.quit_response = '421 QUIT FAILED'
+ def test_with_statement(self):
+ with smtplib.SMTP(HOST, self.port) as smtp:
+ code, message = smtp.noop()
+ self.assertEqual(code, 250)
+ self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo')
+ with smtplib.SMTP(HOST, self.port) as smtp:
+ smtp.close()
+ self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo')
+
+ def test_with_statement_QUIT_failure(self):
- # We don't need to clean up self.serv.quit_response because a new
- # server is always instantiated in the setUp().
+ with self.assertRaises(smtplib.SMTPResponseException) as error:
+ with smtplib.SMTP(HOST, self.port) as smtp:
++ self.serv._SMTPchannel.quit_response = '421 QUIT FAILED'
+ smtp.noop()
+ self.assertEqual(error.exception.smtp_code, 421)
+ self.assertEqual(error.exception.smtp_error, b'QUIT FAILED')
+
#TODO: add tests for correct AUTH method fallback now that the
#test infrastructure can support it.
Library
-------
+ - Issue #5713: smtplib now handles 421 (closing connection) error codes when
+ sending mail by closing the socket and reporting the 421 error code via the
+ exception appropriate to the command that received the error response.
+
+- Issue #17192: Update the ctypes module's libffi to v3.0.13. This
+ specifically addresses a stack misalignment issue on x86 and issues on
+ some more recent platforms.
+
- Issue #8862: Fixed curses cleanup when getkey is interrputed by a signal.
-- Issue #17443: impalib.IMAP4_stream was using the default unbuffered IO
+- Issue #17443: imaplib.IMAP4_stream was using the default unbuffered IO
in subprocess, but the imap code assumes buffered IO. In Python2 this
worked by accident. IMAP4_stream now explicitly uses buffered IO.