Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/library/email.compat32-message.rst
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,15 @@ Here are the methods of the :class:`Message` class:

Content-Disposition: attachment; filename*="iso-8859-1''Fu%DFballer.ppt"

When called without any *_params*, this method stores *_value* the same
way :meth:`__setitem__` does, so *_value* may also be an
:class:`~email.header.Header` instance, such as those returned by
:meth:`items` under a ``compat32`` policy.

.. versionchanged:: next
When no parameters are given, an :class:`~email.header.Header`
*_value* is now accepted, consistent with :meth:`__setitem__`.


.. method:: replace_header(_name, _value)

Expand Down
7 changes: 7 additions & 0 deletions Lib/email/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,13 @@ def add_header(self, _name, _value, **_params):
msg.add_header('content-disposition', 'attachment',
filename='Fußballer.ppt'))
"""
if not _params:
# With no parameters, mirror __setitem__ so add_header() accepts
# the same values, e.g. the Header instances that items() returns
# under the compat32 policy. None is coerced to '' to preserve
# add_header()'s historical behavior (__setitem__ would store None).
self[_name] = '' if _value is None else _value
return
parts = []
for k, v in _params.items():
if v is None:
Expand Down
17 changes: 17 additions & 0 deletions Lib/test/test_email/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,23 @@ def test_add_header_with_no_value(self):
msg.add_header('X-Status', None)
self.assertEqual('', msg['X-Status'])

def test_add_header_copies_Header_returned_by_items(self):
# gh-151454: items() returns (name, Header) under the compat32 policy
# for a header carrying 8-bit data. Copying such headers with the
# bug report's loop (newmsg.add_header(h, v)) used to raise
# "TypeError: sequence item 0: expected str instance, Header found".
old = email.message_from_bytes(b'X-Ham-Report: spam \xff report\n\n')
name, value = old.items()[0]
self.assertIsInstance(value, Header)
msg = Message()
msg.add_header(name, value)
# The Header is stored unmodified, exactly as __setitem__ does (rather
# than stringified, which would lose the 8-bit bytes to U+FFFD).
self.assertIs(next(msg.raw_items())[1], value)
ref = Message()
ref[name] = value
self.assertEqual(msg.as_bytes(), ref.as_bytes())

# Issue 5871: reject an attempt to embed a header inside a header value
# (header injection attack).
def test_embedded_header_via_Header_rejected(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
When called without extra parameters, :meth:`email.message.Message.add_header`
now stores the value the same way ``msg[name] = value`` does, so it accepts the
:class:`~email.header.Header` instances that :meth:`~email.message.Message.items`
returns under the ``compat32`` policy. Previously it raised ``TypeError:
sequence item 0: expected str instance, Header found``.
Loading