Skip to content

Commit d5fbe9b

Browse files
scorphuspablogsal
authored andcommitted
bpo-34246: Use no mutable default args in smtplib (GH-8554)
Some methods of the SMTP class use mutable default arguments. Specially `send_message` is affected as it mutates one of the args by appending items to it, which has side effects on further calls.
1 parent 4e51937 commit d5fbe9b

File tree

5 files changed

+39
-8
lines changed

5 files changed

+39
-8
lines changed

Doc/library/smtplib.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ An :class:`SMTP` instance has the following methods:
419419
:exc:`SMTPException`.
420420

421421

422-
.. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=[], rcpt_options=[])
422+
.. method:: SMTP.sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
423423

424424
Send mail. The required arguments are an :rfc:`822` from-address string, a list
425425
of :rfc:`822` to-address strings (a bare string will be treated as a list with 1
@@ -491,7 +491,7 @@ An :class:`SMTP` instance has the following methods:
491491

492492

493493
.. method:: SMTP.send_message(msg, from_addr=None, to_addrs=None, \
494-
mail_options=[], rcpt_options=[])
494+
mail_options=(), rcpt_options=())
495495

496496
This is a convenience method for calling :meth:`sendmail` with the message
497497
represented by an :class:`email.message.Message` object. The arguments have

Lib/smtplib.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ def noop(self):
513513
"""SMTP 'noop' command -- doesn't do anything :>"""
514514
return self.docmd("noop")
515515

516-
def mail(self, sender, options=[]):
516+
def mail(self, sender, options=()):
517517
"""SMTP 'mail' command -- begins mail xfer session.
518518
519519
This method may raise the following exceptions:
@@ -534,7 +534,7 @@ def mail(self, sender, options=[]):
534534
self.putcmd("mail", "FROM:%s%s" % (quoteaddr(sender), optionlist))
535535
return self.getreply()
536536

537-
def rcpt(self, recip, options=[]):
537+
def rcpt(self, recip, options=()):
538538
"""SMTP 'rcpt' command -- indicates 1 recipient for this mail."""
539539
optionlist = ''
540540
if options and self.does_esmtp:
@@ -785,8 +785,8 @@ def starttls(self, keyfile=None, certfile=None, context=None):
785785
raise SMTPResponseException(resp, reply)
786786
return (resp, reply)
787787

788-
def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
789-
rcpt_options=[]):
788+
def sendmail(self, from_addr, to_addrs, msg, mail_options=(),
789+
rcpt_options=()):
790790
"""This command performs an entire mail transaction.
791791
792792
The arguments are:
@@ -890,7 +890,7 @@ def sendmail(self, from_addr, to_addrs, msg, mail_options=[],
890890
return senderrs
891891

892892
def send_message(self, msg, from_addr=None, to_addrs=None,
893-
mail_options=[], rcpt_options={}):
893+
mail_options=(), rcpt_options=()):
894894
"""Converts message to a bytestring and passes it to sendmail.
895895
896896
The arguments are as for sendmail, except that msg is an
@@ -958,7 +958,7 @@ def send_message(self, msg, from_addr=None, to_addrs=None,
958958
if international:
959959
g = email.generator.BytesGenerator(
960960
bytesmsg, policy=msg.policy.clone(utf8=True))
961-
mail_options += ['SMTPUTF8', 'BODY=8BITMIME']
961+
mail_options = (*mail_options, 'SMTPUTF8', 'BODY=8BITMIME')
962962
else:
963963
g = email.generator.BytesGenerator(bytesmsg)
964964
g.flatten(msg_copy, linesep='\r\n')

Lib/test/test_smtplib.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import unittest
2121
from test import support, mock_socket
2222
from test.support import HOST, HOSTv4, HOSTv6
23+
from unittest.mock import Mock
2324

2425

2526
if sys.platform == 'darwin':
@@ -578,6 +579,33 @@ def testNonnumericPort(self):
578579
"localhost:bogus")
579580

580581

582+
class DefaultArgumentsTests(unittest.TestCase):
583+
584+
def setUp(self):
585+
self.msg = EmailMessage()
586+
self.msg['From'] = 'Páolo <fő[email protected]>'
587+
self.smtp = smtplib.SMTP()
588+
self.smtp.ehlo = Mock(return_value=(200, 'OK'))
589+
self.smtp.has_extn, self.smtp.sendmail = Mock(), Mock()
590+
591+
def testSendMessage(self):
592+
expected_mail_options = ('SMTPUTF8', 'BODY=8BITMIME')
593+
self.smtp.send_message(self.msg)
594+
self.smtp.send_message(self.msg)
595+
self.assertEqual(self.smtp.sendmail.call_args_list[0][0][3],
596+
expected_mail_options)
597+
self.assertEqual(self.smtp.sendmail.call_args_list[1][0][3],
598+
expected_mail_options)
599+
600+
def testSendMessageWithMailOptions(self):
601+
mail_options = ['STARTTLS']
602+
expected_mail_options = ('STARTTLS', 'SMTPUTF8', 'BODY=8BITMIME')
603+
self.smtp.send_message(self.msg, None, None, mail_options)
604+
self.assertEqual(mail_options, ['STARTTLS'])
605+
self.assertEqual(self.smtp.sendmail.call_args_list[0][0][3],
606+
expected_mail_options)
607+
608+
581609
# test response of client to a non-successful HELO message
582610
class BadHELOServerTests(unittest.TestCase):
583611

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Eitan Adler
2222
Anton Afanasyev
2323
Ali Afshar
2424
Nitika Agarwal
25+
Pablo S. Blum de Aguiar
2526
Jim Ahlstrom
2627
Farhan Ahmad
2728
Matthew Ahrens
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:meth:`smtplib.SMTP.send_message` no longer modifies the content of the
2+
*mail_options* argument. Patch by Pablo S. Blum de Aguiar.

0 commit comments

Comments
 (0)