Skip to content
Prev Previous commit
Next Next commit
Update test_smtplib.py from 3.13.11
  • Loading branch information
ShaharNaveh committed Dec 13, 2025
commit 936d9c683c0d1c1bd96bdc2b336ef35df0f9393b
84 changes: 66 additions & 18 deletions Lib/test/test_smtplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import threading

import unittest
import unittest.mock as mock
from test import support, mock_socket
from test.support import hashlib_helper
from test.support import socket_helper
Expand Down Expand Up @@ -350,7 +351,7 @@ def testVRFY(self):
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
expected = (252, b'Cannot VRFY user, but will accept message ' + \
b'and attempt delivery')
b'and attempt delivery')
self.assertEqual(smtp.vrfy('[email protected]'), expected)
self.assertEqual(smtp.verify('[email protected]'), expected)
smtp.quit()
Expand All @@ -371,7 +372,7 @@ def testHELP(self):
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
self.assertEqual(smtp.help(), b'Supported commands: EHLO HELO MAIL ' + \
b'RCPT DATA RSET NOOP QUIT VRFY')
b'RCPT DATA RSET NOOP QUIT VRFY')
smtp.quit()

def testSend(self):
Expand Down Expand Up @@ -527,7 +528,7 @@ def testSendMessageWithAddresses(self):
smtp.quit()
# make sure the Bcc header is still in the message.
self.assertEqual(m['Bcc'], 'John Root <root@localhost>, "Dinsdale" '
'<[email protected]>')
'<[email protected]>')

self.client_evt.set()
self.serv_evt.wait()
Expand Down Expand Up @@ -766,7 +767,7 @@ def tearDown(self):

def testFailingHELO(self):
self.assertRaises(smtplib.SMTPConnectError, smtplib.SMTP,
HOST, self.port, 'localhost', 3)
HOST, self.port, 'localhost', 3)


class TooLongLineTests(unittest.TestCase):
Expand Down Expand Up @@ -804,14 +805,14 @@ def testLineTooLong(self):
sim_users = {'[email protected]':'John A',
'[email protected]':'Sally B',
'[email protected]':'Ruth C',
}
}

sim_auth = ('[email protected]', 'somepassword')
sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn'
'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=')
sim_lists = {'list-1':['[email protected]','[email protected]'],
'list-2':['[email protected]',],
}
}

# Simulated SMTP channel & server
class ResponseException(Exception): pass
Expand All @@ -830,6 +831,7 @@ class SimSMTPChannel(smtpd.SMTPChannel):
def __init__(self, extra_features, *args, **kw):
self._extrafeatures = ''.join(
[ "250-{0}\r\n".format(x) for x in extra_features ])
self.all_received_lines = []
super(SimSMTPChannel, self).__init__(*args, **kw)

# AUTH related stuff. It would be nice if support for this were in smtpd.
Expand All @@ -844,6 +846,7 @@ def found_terminator(self):
self.smtp_state = self.COMMAND
self.push('%s %s' % (e.smtp_code, e.smtp_error))
return
self.all_received_lines.append(self.received_lines)
super().found_terminator()


Expand Down Expand Up @@ -924,11 +927,14 @@ def _auth_cram_md5(self, arg=None):
except ValueError as e:
self.push('535 Splitting response {!r} into user and password '
'failed: {}'.format(logpass, e))
return False
valid_hashed_pass = hmac.HMAC(
sim_auth[1].encode('ascii'),
self._decode_base64(sim_cram_md5_challenge).encode('ascii'),
'md5').hexdigest()
return
pwd = sim_auth[1].encode('ascii')
msg = self._decode_base64(sim_cram_md5_challenge).encode('ascii')
try:
valid_hashed_pass = hmac.HMAC(pwd, msg, 'md5').hexdigest()
except ValueError:
self.push('504 CRAM-MD5 is not supported')
return
self._authenticated(user, hashed_pass == valid_hashed_pass)
# end AUTH related stuff.

Expand Down Expand Up @@ -1170,8 +1176,7 @@ def auth_buggy(challenge=None):
finally:
smtp.close()

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_CRAM_MD5(self):
self.serv.add_feature("AUTH CRAM-MD5")
Expand All @@ -1181,8 +1186,40 @@ def testAUTH_CRAM_MD5(self):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

# TODO: RUSTPYTHON
@unittest.expectedFailure
@mock.patch("hmac.HMAC")
@mock.patch("smtplib._have_cram_md5_support", False)
def testAUTH_CRAM_MD5_blocked(self, hmac_constructor):
# CRAM-MD5 is the only "known" method by the server,
# but it is not supported by the client. In particular,
# no challenge will ever be sent.
self.serv.add_feature("AUTH CRAM-MD5")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
msg = re.escape("No suitable authentication method found.")
with self.assertRaisesRegex(smtplib.SMTPException, msg):
smtp.login(sim_auth[0], sim_auth[1])
hmac_constructor.assert_not_called() # call has been bypassed

@mock.patch("smtplib._have_cram_md5_support", False)
def testAUTH_CRAM_MD5_blocked_and_fallback(self):
# Test that PLAIN is tried after CRAM-MD5 failed
self.serv.add_feature("AUTH CRAM-MD5 PLAIN")
smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)
with (
mock.patch.object(smtp, "auth_cram_md5") as smtp_auth_cram_md5,
mock.patch.object(
smtp, "auth_plain", wraps=smtp.auth_plain
) as smtp_auth_plain
):
resp = smtp.login(sim_auth[0], sim_auth[1])
smtp_auth_plain.assert_called_once()
smtp_auth_cram_md5.assert_not_called() # no call to HMAC constructor
self.assertEqual(resp, (235, b'Authentication Succeeded'))

@unittest.expectedFailure # TODO: RUSTPYTHON
@hashlib_helper.requires_hashdigest('md5', openssl=True)
def testAUTH_multiple(self):
# Test that multiple authentication methods are tried.
Expand All @@ -1193,8 +1230,7 @@ def testAUTH_multiple(self):
self.assertEqual(resp, (235, b'Authentication Succeeded'))
smtp.close()

# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_auth_function(self):
supported = {'PLAIN', 'LOGIN'}
try:
Expand Down Expand Up @@ -1354,6 +1390,18 @@ def test_name_field_not_included_in_envelop_addresses(self):
self.assertEqual(self.serv._addresses['from'], '[email protected]')
self.assertEqual(self.serv._addresses['tos'], ['[email protected]'])

def test_lowercase_mail_from_rcpt_to(self):
m = 'A test message'
smtp = smtplib.SMTP(
HOST, self.port, local_hostname='localhost',
timeout=support.LOOPBACK_TIMEOUT)
self.addCleanup(smtp.close)

smtp.sendmail('John', 'Sally', m)

self.assertIn(['mail from:<John> size=14'], self.serv._SMTPchannel.all_received_lines)
self.assertIn(['rcpt to:<Sally>'], self.serv._SMTPchannel.all_received_lines)


class SimSMTPUTF8Server(SimSMTPServer):

Expand All @@ -1372,7 +1420,7 @@ def handle_accepted(self, conn, addr):
)

def process_message(self, peer, mailfrom, rcpttos, data, mail_options=None,
rcpt_options=None):
rcpt_options=None):
self.last_peer = peer
self.last_mailfrom = mailfrom
self.last_rcpttos = rcpttos
Expand Down
Loading