diff --git a/Lib/smtplib.py b/Lib/smtplib.py index b679875fd2c539..5f98befe3571b4 100755 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -627,7 +627,7 @@ def auth(self, mechanism, authobject, *, initial_response_ok=True): mechanism = mechanism.upper() initial_response = (authobject() if initial_response_ok else None) if initial_response is not None: - response = encode_base64(initial_response.encode('ascii'), eol='') + response = encode_base64(initial_response.encode('utf-8'), eol='') (code, resp) = self.docmd("AUTH", mechanism + " " + response) else: (code, resp) = self.docmd("AUTH", mechanism) @@ -635,7 +635,7 @@ def auth(self, mechanism, authobject, *, initial_response_ok=True): if code == 334: challenge = base64.decodebytes(resp) response = encode_base64( - authobject(challenge).encode('ascii'), eol='') + authobject(challenge).encode('utf-8'), eol='') (code, resp) = self.docmd(response) if code in (235, 503): return (code, resp) @@ -648,7 +648,7 @@ def auth_cram_md5(self, challenge=None): if challenge is None: return None return self.user + " " + hmac.HMAC( - self.password.encode('ascii'), challenge, 'md5').hexdigest() + self.password.encode('utf-8'), challenge, 'md5').hexdigest() def auth_plain(self, challenge=None): """ Authobject to use with PLAIN authentication. Requires self.user and diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 495764f9aca902..380578c3942e1a 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -631,7 +631,13 @@ def testLineTooLong(self): 'Mrs.C@somewhereesle.com':'Ruth C', } -sim_auth = ('Mr.A@somewhere.com', 'somepassword') +# '密码' means password in Chinese. +valid_sim_auths = { + 'Mr.A@somewhere.com': 'somepassword', + 'Ms.D@example.com': '密码'} + +invalid_sim_auth = ('Ms.E@example.com', '\ud800\ud800') + sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn' 'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=') sim_lists = {'list-1':['Mr.A@somewhere.com','Mrs.C@somewhereesle.com'], @@ -706,7 +712,7 @@ def _authenticated(self, user, valid): self.smtp_state = self.COMMAND def _decode_base64(self, string): - return base64.decodebytes(string.encode('ascii')).decode('utf-8') + return base64.decodebytes(string.encode('utf-8')).decode('utf-8') def _auth_plain(self, arg=None): if arg is None: @@ -719,7 +725,7 @@ def _auth_plain(self, arg=None): self.push('535 Splitting response {!r} into user and password' ' failed: {}'.format(logpass, e)) return - self._authenticated(user, password == sim_auth[1]) + self._authenticated(user, password == valid_sim_auths[user]) def _auth_login(self, arg=None): if arg is None: @@ -731,7 +737,7 @@ def _auth_login(self, arg=None): self.push('334 UGFzc3dvcmQ6') else: password = self._decode_base64(arg) - self._authenticated(self._auth_login_user, password == sim_auth[1]) + self._authenticated(self._auth_login_user, password == valid_sim_auths[self._auth_login_user]) del self._auth_login_user def _auth_cram_md5(self, arg=None): @@ -746,8 +752,8 @@ def _auth_cram_md5(self, arg=None): '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'), + valid_sim_auths[user].encode('utf-8'), + self._decode_base64(sim_cram_md5_challenge).encode('utf-8'), 'md5').hexdigest() self._authenticated(user, hashed_pass == valid_hashed_pass) # end AUTH related stuff. @@ -920,7 +926,7 @@ def testEXPN(self): users = [] for m in members: users.append('%s %s' % (sim_users[m], smtplib.quoteaddr(m))) - expected_known = (250, bytes('\n'.join(users), "ascii")) + expected_known = (250, bytes('\n'.join(users), "utf-8")) self.assertEqual(smtp.expn(listname), expected_known) u = 'PSU-Members-List' @@ -928,49 +934,74 @@ def testEXPN(self): self.assertEqual(smtp.expn(u), expected_unknown) smtp.quit() - def testAUTH_PLAIN(self): + def testAUTH_PLAIN_UnicodeEncodeError(self): self.serv.add_feature("AUTH PLAIN") smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) - resp = smtp.login(sim_auth[0], sim_auth[1]) - self.assertEqual(resp, (235, b'Authentication Succeeded')) + with self.assertRaises(UnicodeEncodeError): + resp = smtp.login(invalid_sim_auth[0], invalid_sim_auth[1]) smtp.close() + def testAUTH_PLAIN(self): + self.serv.add_feature("AUTH PLAIN") + for username, password in valid_sim_auths.items(): + with self.subTest(username=username, password=password): + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) + resp = smtp.login(username, password) + self.assertEqual(resp, (235, b'Authentication Succeeded')) + smtp.close() + def testAUTH_LOGIN(self): self.serv.add_feature("AUTH LOGIN") - smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) - resp = smtp.login(sim_auth[0], sim_auth[1]) - self.assertEqual(resp, (235, b'Authentication Succeeded')) - smtp.close() + for username, password in valid_sim_auths.items(): + with self.subTest(username=username, password=password): + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) + resp = smtp.login(username, password) + self.assertEqual(resp, (235, b'Authentication Succeeded')) + smtp.close() + + def testAUTH_LOGIN_wrong_password(self): + self.serv.add_feature("AUTH LOGIN") + for username, password in valid_sim_auths.items(): + with self.subTest(username=username, password=password): + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) + with self.assertRaises(smtplib.SMTPAuthenticationError): + resp = smtp.login(username, 'random') + smtp.close() def testAUTH_CRAM_MD5(self): self.serv.add_feature("AUTH CRAM-MD5") - smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) - resp = smtp.login(sim_auth[0], sim_auth[1]) - self.assertEqual(resp, (235, b'Authentication Succeeded')) - smtp.close() + for username, password in valid_sim_auths.items(): + with self.subTest(username=username, password=password): + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) + resp = smtp.login(username, password) + self.assertEqual(resp, (235, b'Authentication Succeeded')) + smtp.close() def testAUTH_multiple(self): # Test that multiple authentication methods are tried. self.serv.add_feature("AUTH BOGUS PLAIN LOGIN CRAM-MD5") - smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) - resp = smtp.login(sim_auth[0], sim_auth[1]) - self.assertEqual(resp, (235, b'Authentication Succeeded')) - smtp.close() + for username, password in valid_sim_auths.items(): + with self.subTest(username=username, password=password): + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) + resp = smtp.login(username, password) + self.assertEqual(resp, (235, b'Authentication Succeeded')) + smtp.close() def test_auth_function(self): supported = {'CRAM-MD5', 'PLAIN', 'LOGIN'} for mechanism in supported: self.serv.add_feature("AUTH {}".format(mechanism)) for mechanism in supported: - with self.subTest(mechanism=mechanism): - smtp = smtplib.SMTP(HOST, self.port, - local_hostname='localhost', timeout=15) - smtp.ehlo('foo') - smtp.user, smtp.password = sim_auth[0], sim_auth[1] - method = 'auth_' + mechanism.lower().replace('-', '_') - resp = smtp.auth(mechanism, getattr(smtp, method)) - self.assertEqual(resp, (235, b'Authentication Succeeded')) - smtp.close() + for username, password in valid_sim_auths.items(): + with self.subTest(mechanism=mechanism): + smtp = smtplib.SMTP(HOST, self.port, + local_hostname='localhost', timeout=15) + smtp.ehlo('foo') + smtp.user, smtp.password = username, password + method = 'auth_' + mechanism.lower().replace('-', '_') + resp = smtp.auth(mechanism, getattr(smtp, method)) + self.assertEqual(resp, (235, b'Authentication Succeeded')) + smtp.close() def test_quit_resets_greeting(self): smtp = smtplib.SMTP(HOST, self.port, diff --git a/Misc/NEWS.d/next/Library/2019-08-01-04-13-26.bpo-29750.jJGCMv.rst b/Misc/NEWS.d/next/Library/2019-08-01-04-13-26.bpo-29750.jJGCMv.rst new file mode 100644 index 00000000000000..2aad1420201699 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-08-01-04-13-26.bpo-29750.jJGCMv.rst @@ -0,0 +1 @@ +Let smtplib support non-ASCII passwords \ No newline at end of file