diff -r c52807b17e03 Doc/library/smtplib.rst --- a/Doc/library/smtplib.rst Wed May 18 00:20:18 2011 +0200 +++ b/Doc/library/smtplib.rst Wed May 18 07:49:00 2011 +0200 @@ -344,16 +344,21 @@ the same meaning as for :meth:`sendmail`, except that *msg* is a ``Message`` object. - If *from_addr* is ``None``, ``send_message`` sets its value to the value of - the :mailheader:`From` header from *msg*. If *to_addrs* is ``None``, - ``send_message`` combines the values (if any) of the :mailheader:`To`, - :mailheader:`CC`, and :mailheader:`Bcc` fields from *msg*. Regardless of - the values of *from_addr* and *to_addrs*, ``send_message`` deletes any Bcc - field from *msg*. It then serializes *msg* using - :class:`~email.generator.BytesGenerator` with ``\r\n`` as the *linesep*, and - calls :meth:`sendmail` to transmit the resulting message. + If *from_addr* is ``None`` or *to_addrs* is ``None``, ``send_message`` fills + those arguments with addresses extracted from the headers of *msg* as + specified in :rfc:`2822`. In a nutshell *from_addr* sets its value to the + :mailheader:`Sender` field if present or the :mailheader:`From` field, and + *to_adresses* combines the values (if any) of the :mailheader:`To`, + :mailheader:`Cc`, and :mailheader:`Bcc` fields from *msg*. Regardless of + the values of *from_addr* and *to_addrs*, ``send_message`` deletes any + :mailheader:`Bcc` field from *msg*. If *msg* is a bounced message, some + :mailheader:`Resent-*` fields appear in the headers ; in such a case, those + fields are considered instead of the previous ones. It then serializes + *msg* using :class:`~email.generator.BytesGenerator` with ``\r\n`` as the + *linesep*, and calls :meth:`sendmail` to transmit the resulting message. .. versionadded:: 3.2 + .. versionchanged:: 3.3 Extraction of addresses has been made :rfc:`2822` compliant. .. method:: SMTP.quit() diff -r c52807b17e03 Lib/smtplib.py --- a/Lib/smtplib.py Wed May 18 00:20:18 2011 +0200 +++ b/Lib/smtplib.py Wed May 18 07:49:00 2011 +0200 @@ -687,7 +687,7 @@ msg may be a string containing characters in the ASCII range, or a byte string. A string is encoded to bytes using the ascii codec, and lone - \r and \n characters are converted to \r\n characters. + \\r and \\n characters are converted to \\r\\n characters. If there has been no previous EHLO or HELO command this session, this method tries ESMTP EHLO first. If the server does ESMTP, message size @@ -770,21 +770,31 @@ """Converts message to a bytestring and passes it to sendmail. The arguments are as for sendmail, except that msg is an - email.message.Message object. If from_addr is None, the from_addr is - taken from the 'From' header of the Message. If to_addrs is None, its - value is composed from the addresses listed in the 'To', 'CC', and - 'Bcc' fields. Regardless of the values of from_addr and to_addr, any - Bcc field in the Message object is deleted. The Message object is then + email.message.Message object. If from_addr is None or to_addrs is + None, these arguments are taken from the headers of the Message as + described in RFC 2822. Regardless of the values of from_addr and + to_addr, any Bcc field (or Resent-Bcc field, when the Message is a + resent) of the Message object is deleted. The Message object is then serialized using email.generator.BytesGenerator and sendmail is called to transmit the message. + """ + + is_resent = 'Resent-' if 'Resent-Date' in msg else '' + # 'Resent-Date' is a mandatory field if the Message is resent + # (RFC 2822 Section 3.6.6). In such a case, we must consider the + # 'Resent-*' fields. if from_addr is None: - from_addr = msg['From'] + from_addr = (msg[is_resent+'Sender'] if (is_resent+'Sender') in msg + else msg[is_resent+'From']) + # 'Sender' field has to be prefered to the 'From' field if + # present (RFC 2822 Section 3.6.2) if to_addrs is None: - addr_fields = [f for f in (msg['To'], msg['Bcc'], msg['CC']) - if f is not None] + addr_fields = [f for f in (msg[is_resent+'To'], + msg[is_resent+'Bcc'], + msg[is_resent+'Cc']) if f is not None] to_addrs = [a[1] for a in email.utils.getaddresses(addr_fields)] - del msg['Bcc'] + del msg[is_resent+'Bcc'] # (RFC 2822 Section 3.6.3) with io.BytesIO() as bytesmsg: g = email.generator.BytesGenerator(bytesmsg) g.flatten(msg, linesep='\r\n') diff -r c52807b17e03 Lib/test/test_smtplib.py --- a/Lib/test/test_smtplib.py Wed May 18 00:20:18 2011 +0200 +++ b/Lib/test/test_smtplib.py Wed May 18 07:49:00 2011 +0200 @@ -350,6 +350,65 @@ re.MULTILINE) self.assertRegex(debugout, to_addr) + def testSendMessageWithMultipleFrom(self): + # Make sure nothing breaks if not all of the three 'to' headers exist + m = email.mime.text.MIMEText('A test message') + m['From'] = 'Bernard, Bianca' + m['Sender'] = 'the_rescuers@Rescue-Aid-Society.com' + m['To'] = 'John, Dinsdale' + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3) + smtp.send_message(m) + # XXX (see comment in testSend) + time.sleep(0.01) + smtp.quit() + + self.client_evt.set() + self.serv_evt.wait() + self.output.flush() + # Add the X-Peer header that DebuggingServer adds + m['X-Peer'] = socket.gethostbyname('localhost') + mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END) + self.assertEqual(self.output.getvalue(), mexpect) + debugout = smtpd.DEBUGSTREAM.getvalue() + sender = re.compile("^sender: the_rescuers@Rescue-Aid-Society.com$", re.MULTILINE) + self.assertRegex(debugout, sender) + for addr in ('John', 'Dinsdale'): + to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr), + re.MULTILINE) + self.assertRegex(debugout, to_addr) + + def testSendMessageResent(self): + m = email.mime.text.MIMEText('A test message') + m['From'] = 'foo@bar.com' + m['To'] = 'John' + m['CC'] = 'Sally, Fred' + m['Bcc'] = 'John Root , "Dinsdale" ' + m['Resent-Date'] = 'Thu, 1 Jan 1970 17:42:00 +0000' + m['Resent-From'] = 'holy@grail.net' + m['Resent-To'] = 'Martha , Jeff' + m['Resent-Bcc'] = 'doe@losthope.net' + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=3) + smtp.send_message(m) + # XXX (see comment in testSend) + time.sleep(0.01) + smtp.quit() + + self.client_evt.set() + self.serv_evt.wait() + self.output.flush() + # The Resent-Bcc header is deleted before serialization. + del m['Resent-Bcc'] + # Add the X-Peer header that DebuggingServer adds + m['X-Peer'] = socket.gethostbyname('localhost') + mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END) + self.assertEqual(self.output.getvalue(), mexpect) + debugout = smtpd.DEBUGSTREAM.getvalue() + sender = re.compile("^sender: holy@grail.net$", re.MULTILINE) + self.assertRegex(debugout, sender) + for addr in ('my_mom@great.cooker.com', 'Jeff', 'doe@losthope.net'): + to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr), + re.MULTILINE) + self.assertRegex(debugout, to_addr) class NonConnectingTests(unittest.TestCase): diff -r c52807b17e03 Misc/ACKS --- a/Misc/ACKS Wed May 18 00:20:18 2011 +0200 +++ b/Misc/ACKS Wed May 18 07:49:00 2011 +0200 @@ -265,6 +265,7 @@ Ben Escoto Andy Eskilsson Stefan Esser +Nicolas Estibals Stephen D Evans Carey Evans Tim Everett