Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
db4120bf
Commit
db4120bf
authored
Jul 02, 2011
by
R David Murray
Browse files
Options
Browse Files
Download
Plain Diff
merge #12147: make send_message correctly handle Sender and Resent- headers.
parents
020436b0
ac4e5abc
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
172 additions
and
22 deletions
+172
-22
Doc/library/smtplib.rst
Doc/library/smtplib.rst
+19
-8
Lib/smtplib.py
Lib/smtplib.py
+39
-13
Lib/test/test_smtplib.py
Lib/test/test_smtplib.py
+110
-1
Misc/ACKS
Misc/ACKS
+1
-0
Misc/NEWS
Misc/NEWS
+3
-0
No files found.
Doc/library/smtplib.rst
View file @
db4120bf
...
...
@@ -348,21 +348,32 @@ An :class:`SMTP` instance has the following methods:
.. versionchanged:: 3.2 *msg* may be a byte string.
.. method:: SMTP.send_message(msg, from_addr=None, to_addrs=None, mail_options=[], rcpt_options=[])
.. method:: SMTP.send_message(msg, from_addr=None, to_addrs=None, \
mail_options=[], rcpt_options=[])
This is a convenience method for calling :meth:`sendmail` with the message
represented by an :class:`email.message.Message` object. The arguments have
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
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`\: *from_addr* is set to the :mailheader:`Sender`
field if it is present, and otherwise to the :mailheader:`From` field.
*to_adresses* combines the values (if any) of the :mailheader:`To`,
:mailheader:`Cc`, and :mailheader:`Bcc` fields from *msg*. If exactly one
set of :mailheader:`Resent-*` headers appear in the message, the regular
headers are ignored and the :mailheader:`Resent-*` headers are used instead.
If the message contains more than one set of :mailheader:`Resent-*` headers,
a :exc:`ValueError` is raised, since there is no way to unambiguously detect
the most recent set of :mailheader:`Resent-` headers.
``send_message`` serializes *msg* using
:class:`~email.generator.BytesGenerator` with ``\r\n`` as the *linesep*, and
calls :meth:`sendmail` to transmit the resulting message.
calls :meth:`sendmail` to transmit the resulting message. Regardless of the
values of *from_addr* and *to_addrs*, ``send_message`` does not transmit any
:mailheader:`Bcc` or :mailheader:`Resent-Bcc` headers that may appear
in *msg*.
.. versionadded:: 3.2
...
...
Lib/smtplib.py
100755 → 100644
View file @
db4120bf
...
...
@@ -49,6 +49,7 @@ import email.message
import
email.generator
import
base64
import
hmac
import
copy
from
email.base64mime
import
body_encode
as
encode_base64
from
sys
import
stderr
...
...
@@ -676,7 +677,7 @@ class SMTP:
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
...
...
@@ -759,24 +760,49 @@ class SMTP:
"""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
serialized using email.generator.BytesGenerator and sendmail is called
to transmit the message.
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 (a ValueError is raised if there is more than
one set of 'Resent-' headers). 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 won't be transmitted. The Message
object is then serialized using email.generator.BytesGenerator and
sendmail is called to transmit the message.
"""
# 'Resent-Date' is a mandatory field if the Message is resent (RFC 2822
# Section 3.6.6). In such a case, we use the 'Resent-*' fields. However,
# if there is more than one 'Resent-' block there's no way to
# unambiguously determine which one is the most recent in all cases,
# so rather than guess we raise a ValueError in that case.
#
# TODO implement heuristics to guess the correct Resent-* block with an
# option allowing the user to enable the heuristics. (It should be
# possible to guess correctly almost all of the time.)
resent
=
msg
.
get_all
(
'Resent-Date'
)
if
resent
is
None
:
header_prefix
=
''
elif
len
(
resent
)
==
1
:
header_prefix
=
'Resent-'
else
:
raise
ValueError
(
"message has more than one 'Resent-' header block"
)
if
from_addr
is
None
:
from_addr
=
msg
[
'From'
]
# Prefer the sender field per RFC 2822:3.6.2.
from_addr
=
(
msg
[
header_prefix
+
'Sender'
]
if
(
header_prefix
+
'Sender'
)
in
msg
else
msg
[
header_prefix
+
'From'
])
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
[
header_prefix
+
'To'
],
msg
[
header_prefix
+
'Bcc'
],
msg
[
header_prefix
+
'Cc'
])
if
f
is
not
None
]
to_addrs
=
[
a
[
1
]
for
a
in
email
.
utils
.
getaddresses
(
addr_fields
)]
del
msg
[
'Bcc'
]
# Make a local copy so we can delete the bcc headers.
msg_copy
=
copy
.
copy
(
msg
)
del
msg_copy
[
'Bcc'
]
del
msg_copy
[
'Resent-Bcc'
]
with
io
.
BytesIO
()
as
bytesmsg
:
g
=
email
.
generator
.
BytesGenerator
(
bytesmsg
)
g
.
flatten
(
msg
,
linesep
=
'
\
r
\
n
'
)
g
.
flatten
(
msg
_copy
,
linesep
=
'
\
r
\
n
'
)
flatmsg
=
bytesmsg
.
getvalue
()
return
self
.
sendmail
(
from_addr
,
to_addrs
,
flatmsg
,
mail_options
,
rcpt_options
)
...
...
Lib/test/test_smtplib.py
View file @
db4120bf
...
...
@@ -320,13 +320,16 @@ class DebuggingServerTests(unittest.TestCase):
# XXX (see comment in testSend)
time
.
sleep
(
0.01
)
smtp
.
quit
()
# make sure the Bcc header is still in the message.
self
.
assertEqual
(
m
[
'Bcc'
],
'John Root <root@localhost>, "Dinsdale" '
'<warped@silly.walks.com>'
)
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'
)
# The Bcc header
is deleted before serialization
.
# The Bcc header
should not be transmitted
.
del
m
[
'Bcc'
]
mexpect
=
'%s%s
\
n
%s'
%
(
MSG_BEGIN
,
m
.
as_string
(),
MSG_END
)
self
.
assertEqual
(
self
.
output
.
getvalue
(),
mexpect
)
...
...
@@ -365,6 +368,112 @@ class DebuggingServerTests(unittest.TestCase):
re
.
MULTILINE
)
self
.
assertRegex
(
debugout
,
to_addr
)
def
testSendMessageWithSpecifiedAddresses
(
self
):
# Make sure addresses specified in call override those in message.
m
=
email
.
mime
.
text
.
MIMEText
(
'A test message'
)
m
[
'From'
]
=
'foo@bar.com'
m
[
'To'
]
=
'John, Dinsdale'
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
3
)
smtp
.
send_message
(
m
,
from_addr
=
'joe@example.com'
,
to_addrs
=
'foo@example.net'
)
# 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: joe@example.com$"
,
re
.
MULTILINE
)
self
.
assertRegex
(
debugout
,
sender
)
for
addr
in
(
'John'
,
'Dinsdale'
):
to_addr
=
re
.
compile
(
r"^recips: .*'{}'.*$"
.
format
(
addr
),
re
.
MULTILINE
)
self
.
assertNotRegex
(
debugout
,
to_addr
)
recip
=
re
.
compile
(
r"^recips: .*'foo@example.net'.*$"
,
re
.
MULTILINE
)
self
.
assertRegex
(
debugout
,
recip
)
def
testSendMessageWithMultipleFrom
(
self
):
# Sender overrides To
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 <root@localhost>, "Dinsdale" <warped@silly.walks.com>'
m
[
'Resent-Date'
]
=
'Thu, 1 Jan 1970 17:42:00 +0000'
m
[
'Resent-From'
]
=
'holy@grail.net'
m
[
'Resent-To'
]
=
'Martha <my_mom@great.cooker.com>, 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 headers are deleted before serialization.
del
m
[
'Bcc'
]
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
)
def
testSendMessageMultipleResentRaises
(
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 <root@localhost>, "Dinsdale" <warped@silly.walks.com>'
m
[
'Resent-Date'
]
=
'Thu, 1 Jan 1970 17:42:00 +0000'
m
[
'Resent-From'
]
=
'holy@grail.net'
m
[
'Resent-To'
]
=
'Martha <my_mom@great.cooker.com>, Jeff'
m
[
'Resent-Bcc'
]
=
'doe@losthope.net'
m
[
'Resent-Date'
]
=
'Thu, 2 Jan 1970 17:42:00 +0000'
m
[
'Resent-To'
]
=
'holy@grail.net'
m
[
'Resent-From'
]
=
'Martha <my_mom@great.cooker.com>, Jeff'
smtp
=
smtplib
.
SMTP
(
HOST
,
self
.
port
,
local_hostname
=
'localhost'
,
timeout
=
3
)
with
self
.
assertRaises
(
ValueError
):
smtp
.
send_message
(
m
)
smtp
.
close
()
class
NonConnectingTests
(
unittest
.
TestCase
):
...
...
Misc/ACKS
View file @
db4120bf
...
...
@@ -278,6 +278,7 @@ Ben Escoto
Andy Eskilsson
André Espaze
Stefan Esser
Nicolas Estibals
Stephen D Evans
Carey Evans
Tim Everett
...
...
Misc/NEWS
View file @
db4120bf
...
...
@@ -203,6 +203,9 @@ Core and Builtins
Library
-------
- Issue #12147: Adjust the new-in-3.2 smtplib.send_message method for better
conformance to the RFCs: correctly handle Sender and Resent- headers.
- Issue #12352: Fix a deadlock in multiprocessing.Heap when a block is freed by
the garbage collector while the Heap lock is held.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment