Index: Lib/email/test/test_email.py =================================================================== --- Lib/email/test/test_email.py (revision 85670) +++ Lib/email/test/test_email.py (working copy) @@ -2590,13 +2590,14 @@ eq(msg.get_payload(), "Here's the message body\n") def test_crlf_separation(self): + # msg_26.txt is a file that has \r\n line endings. eq = self.assertEqual with openfile('msg_26.txt', newline='\n') as fp: msg = Parser().parse(fp) eq(len(msg.get_payload()), 2) part1 = msg.get_payload(0) eq(part1.get_content_type(), 'text/plain') - eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n') + eq(part1.get_payload(), 'Simple email with attachment.\n\n') part2 = msg.get_payload(1) eq(part2.get_content_type(), 'application/riscos') @@ -2931,6 +2932,27 @@ m = bfp.close() self.assertEqual(str(m), self.latin_bin_msg_as7bit) + crlf_bin_msg = textwrap.dedent("""\ + From: foo@bar.com\r + To: test\r + Mime-Version: 1.0\r + Content-Type: text/plain; charset="ascii"\r + \r + Body text\r + should translate\r + crlf\r + """).encode('ascii') + + def test_bytes_parser_translates_crlf_input(self): + # Issue 10134: bytes input was not translating \r\n to \n. + # (See also issue 724459.) + m = email.message_from_bytes(self.crlf_bin_msg) + out = BytesIO() + email.generator.BytesGenerator(out).flatten(m) + self.assertEqual(out.getvalue(), + self.crlf_bin_msg.replace(b'\r\n', b'\n')) + + class TestBytesGeneratorIdempotent(TestIdempotent): @@ -2944,7 +2966,8 @@ b = BytesIO() g = email.generator.BytesGenerator(b, maxheaderlen=0) g.flatten(msg) - self.assertEqual(data, b.getvalue()) + # This isn't a true inversion; email translates \r\n into \n. + self.assertEqual(data.replace(b'\r\n', b'\n'), b.getvalue()) maxDiff = None Index: Lib/email/feedparser.py =================================================================== --- Lib/email/feedparser.py (revision 85670) +++ Lib/email/feedparser.py (working copy) @@ -57,6 +57,7 @@ self._eofstack = [] # A flag indicating whether the file has been closed or not. self._closed = False + self._crlf_file = None def push_eof_matcher(self, pred): self._eofstack.append(pred) @@ -78,6 +79,14 @@ # Pop the line off the stack and see if it matches the current # false-EOF predicate. line = self._lines.pop() + # Assume the line ending of the first line parsed is the + # platform line ending. After that, any time we see that + # ending we convert it to '\n', which is what email uses + # internally. + if self._crlf_file is None: + self._crlf_file = line.endswith('\r\n') + elif self._crlf_file and line.endswith('\r\n'): + line = line[:-2] + '\n' # RFC 2046, section 5.1.2 requires us to recognize outer level # boundaries at any level of inner nesting. Do this, but be sure it's # in the order of most to least nested.