Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 905c8c3

Browse files
committed
#19772: Do not mutate message when downcoding to 7bit.
This is a bit of an ugly hack because of the way generator pieces together the output message. The deepcopys aren't too expensive, though, because we know it is only called on messages that are not multiparts, and the payload (the thing that could be large) is an immutable object. Test and preliminary work on patch by Vajrasky Kok.
1 parent 7c389e2 commit 905c8c3

3 files changed

Lines changed: 28 additions & 1 deletion

File tree

Lib/email/generator.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import random
1313
import warnings
1414

15+
from copy import deepcopy
1516
from io import StringIO, BytesIO
1617
from email._policybase import compat32
1718
from email.header import Header
@@ -173,10 +174,18 @@ def _write(self, msg):
173174
# necessary.
174175
oldfp = self._fp
175176
try:
177+
self._munge_cte = None
176178
self._fp = sfp = self._new_buffer()
177179
self._dispatch(msg)
178180
finally:
179181
self._fp = oldfp
182+
munge_cte = self._munge_cte
183+
del self._munge_cte
184+
# If we munged the cte, copy the message again and re-fix the CTE.
185+
if munge_cte:
186+
msg = deepcopy(msg)
187+
msg.replace_header('content-transfer-encoding', munge_cte[0])
188+
msg.replace_header('content-type', munge_cte[1])
180189
# Write the headers. First we see if the message object wants to
181190
# handle that itself. If not, we'll do it generically.
182191
meth = getattr(msg, '_write_headers', None)
@@ -225,9 +234,14 @@ def _handle_text(self, msg):
225234
if _has_surrogates(msg._payload):
226235
charset = msg.get_param('charset')
227236
if charset is not None:
237+
# XXX: This copy stuff is an ugly hack to avoid modifying the
238+
# existing message.
239+
msg = deepcopy(msg)
228240
del msg['content-transfer-encoding']
229241
msg.set_payload(payload, charset)
230242
payload = msg.get_payload()
243+
self._munge_cte = (msg['content-transfer-encoding'],
244+
msg['content-type'])
231245
if self._mangle_from_:
232246
payload = fcre.sub('>From ', payload)
233247
self._write_lines(payload)

Lib/test/test_email/test_email.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3495,7 +3495,7 @@ def test_CRLFLF_at_end_of_part(self):
34953495
self.assertTrue(msg.get_payload(0).get_payload().endswith('\r\n'))
34963496

34973497

3498-
class Test8BitBytesHandling(unittest.TestCase):
3498+
class Test8BitBytesHandling(TestEmailBase):
34993499
# In Python3 all input is string, but that doesn't work if the actual input
35003500
# uses an 8bit transfer encoding. To hack around that, in email 5.1 we
35013501
# decode byte streams using the surrogateescape error handler, and
@@ -3748,6 +3748,16 @@ def test_generator_handles_8bit(self):
37483748
email.generator.Generator(out).flatten(msg)
37493749
self.assertEqual(out.getvalue(), self.non_latin_bin_msg_as7bit_wrapped)
37503750

3751+
def test_str_generator_should_not_mutate_msg_when_handling_8bit(self):
3752+
msg = email.message_from_bytes(self.non_latin_bin_msg)
3753+
out = BytesIO()
3754+
BytesGenerator(out).flatten(msg)
3755+
orig_value = out.getvalue()
3756+
Generator(StringIO()).flatten(msg) # Should not mutate msg!
3757+
out = BytesIO()
3758+
BytesGenerator(out).flatten(msg)
3759+
self.assertEqual(out.getvalue(), orig_value)
3760+
37513761
def test_bytes_generator_with_unix_from(self):
37523762
# The unixfrom contains a current date, so we can't check it
37533763
# literally. Just make sure the first word is 'From' and the

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ Core and Builtins
4848
Library
4949
-------
5050

51+
- Issue #19772: email.generator no longer mutates the message object when
52+
doing a down-transform from 8bit to 7bit CTEs.
53+
5154
- Issue #18805: the netmask/hostmask parsing in ipaddress now more reliably
5255
filters out illegal values and correctly allows any valid prefix length.
5356

0 commit comments

Comments
 (0)