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

Skip to content

Commit e67c6c5

Browse files
committed
#14645: Generator now emits correct linesep for all parts.
Previously the parts of the message retained whatever linesep they had on read, which means if the messages weren't read in univeral newline mode, the line endings could well be inconsistent. In general sending it via smtplib would result in them getting fixed, but it is better to generate them correctly to begin with. Also, the new send_message method of smtplib does not do the fixup, so that method is producing rfc-invalid output without this fix.
1 parent 697e7ba commit e67c6c5

3 files changed

Lines changed: 58 additions & 4 deletions

File tree

Lib/email/generator.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,19 @@ def _encode(self, s):
119119
# BytesGenerator overrides this to encode strings to bytes.
120120
return s
121121

122+
def _write_lines(self, lines):
123+
# We have to transform the line endings.
124+
if not lines:
125+
return
126+
lines = lines.splitlines(True)
127+
for line in lines[:-1]:
128+
self.write(line.rstrip('\r\n'))
129+
self.write(self._NL)
130+
laststripped = lines[-1].rstrip('\r\n')
131+
self.write(laststripped)
132+
if len(lines[-1])!=len(laststripped):
133+
self.write(self._NL)
134+
122135
def _write(self, msg):
123136
# We can't write the headers yet because of the following scenario:
124137
# say a multipart message includes the boundary string somewhere in
@@ -198,7 +211,7 @@ def _handle_text(self, msg):
198211
payload = msg.get_payload()
199212
if self._mangle_from_:
200213
payload = fcre.sub('>From ', payload)
201-
self.write(payload)
214+
self._write_lines(payload)
202215

203216
# Default body handler
204217
_writeBody = _handle_text
@@ -237,7 +250,8 @@ def _handle_multipart(self, msg):
237250
preamble = fcre.sub('>From ', msg.preamble)
238251
else:
239252
preamble = msg.preamble
240-
self.write(preamble + self._NL)
253+
self._write_lines(preamble)
254+
self.write(self._NL)
241255
# dash-boundary transport-padding CRLF
242256
self.write('--' + boundary + self._NL)
243257
# body-part
@@ -259,7 +273,7 @@ def _handle_multipart(self, msg):
259273
epilogue = fcre.sub('>From ', msg.epilogue)
260274
else:
261275
epilogue = msg.epilogue
262-
self.write(epilogue)
276+
self._write_lines(epilogue)
263277

264278
def _handle_multipart_signed(self, msg):
265279
# The contents of signed parts has to stay unmodified in order to keep
@@ -393,7 +407,7 @@ def _handle_text(self, msg):
393407
if _has_surrogates(msg._payload):
394408
if self._mangle_from_:
395409
msg._payload = fcre.sub(">From ", msg._payload)
396-
self.write(msg._payload)
410+
self._write_lines(msg._payload)
397411
else:
398412
super(BytesGenerator,self)._handle_text(msg)
399413

Lib/email/test/test_email.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def _msgobj(self, filename):
6868
with openfile(findfile(filename)) as fp:
6969
return email.message_from_file(fp)
7070

71+
maxDiff = None
7172

7273

7374
# Test various aspects of the Message class's API
@@ -2907,6 +2908,40 @@ def test_make_msgid_domain(self):
29072908
email.utils.make_msgid(domain='testdomain-string')[-19:],
29082909
'@testdomain-string>')
29092910

2911+
def test_Generator_linend(self):
2912+
# Issue 14645.
2913+
with openfile('msg_26.txt', newline='\n') as f:
2914+
msgtxt = f.read()
2915+
msgtxt_nl = msgtxt.replace('\r\n', '\n')
2916+
msg = email.message_from_string(msgtxt)
2917+
s = StringIO()
2918+
g = email.generator.Generator(s)
2919+
g.flatten(msg)
2920+
self.assertEqual(s.getvalue(), msgtxt_nl)
2921+
2922+
def test_BytesGenerator_linend(self):
2923+
# Issue 14645.
2924+
with openfile('msg_26.txt', newline='\n') as f:
2925+
msgtxt = f.read()
2926+
msgtxt_nl = msgtxt.replace('\r\n', '\n')
2927+
msg = email.message_from_string(msgtxt_nl)
2928+
s = BytesIO()
2929+
g = email.generator.BytesGenerator(s)
2930+
g.flatten(msg, linesep='\r\n')
2931+
self.assertEqual(s.getvalue().decode('ascii'), msgtxt)
2932+
2933+
def test_BytesGenerator_linend_with_non_ascii(self):
2934+
# Issue 14645.
2935+
with openfile('msg_26.txt', 'rb') as f:
2936+
msgtxt = f.read()
2937+
msgtxt = msgtxt.replace(b'with attachment', b'fo\xf6')
2938+
msgtxt_nl = msgtxt.replace(b'\r\n', b'\n')
2939+
msg = email.message_from_bytes(msgtxt_nl)
2940+
s = BytesIO()
2941+
g = email.generator.BytesGenerator(s)
2942+
g.flatten(msg, linesep='\r\n')
2943+
self.assertEqual(s.getvalue(), msgtxt)
2944+
29102945

29112946
# Test the iterator/generators
29122947
class TestIterators(TestEmailBase):

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ Core and Builtins
233233
Library
234234
-------
235235

236+
- Issue #14645: The email generator classes now produce output using the
237+
specified linesep throughout. Previously if the prolog, epilog, or
238+
body were stored with a different linesep, that linesep was used. This
239+
fix corrects an RFC non-compliance issue with smtplib.send_message.
240+
236241
- Issue #17278: Fix a crash in heapq.heappush() and heapq.heappop() when
237242
the list is being resized concurrently.
238243

0 commit comments

Comments
 (0)