diff -r b730baee0877 Doc/library/email.errors.rst --- a/Doc/library/email.errors.rst Tue May 24 16:39:23 2016 -0700 +++ b/Doc/library/email.errors.rst Thu May 26 16:19:36 2016 +0200 @@ -73,6 +73,9 @@ * :class:`NoBoundaryInMultipartDefect` -- A message claimed to be a multipart, but had no :mimetype:`boundary` parameter. +* :class:`NotUniqueBoundaryInMultipartDefect` -- A nested multipart message is + using the same :mimetype:`boundary` for both an outer and an inner entity. + * :class:`StartBoundaryNotFoundDefect` -- The start boundary claimed in the :mailheader:`Content-Type` header was never found. diff -r b730baee0877 Lib/email/errors.py --- a/Lib/email/errors.py Tue May 24 16:39:23 2016 -0700 +++ b/Lib/email/errors.py Thu May 26 16:19:36 2016 +0200 @@ -41,6 +41,9 @@ class NoBoundaryInMultipartDefect(MessageDefect): """A message claimed to be a multipart but had no boundary parameter.""" +class NotUniqueBoundaryInMultipartDefect(MessageDefect): + """A nested multipart entity is using an outer entity boundary.""" + class StartBoundaryNotFoundDefect(MessageDefect): """The claimed start boundary was never found.""" diff -r b730baee0877 Lib/email/feedparser.py --- a/Lib/email/feedparser.py Tue May 24 16:39:23 2016 -0700 +++ b/Lib/email/feedparser.py Thu May 26 16:19:36 2016 +0200 @@ -318,6 +318,16 @@ lines.append(line) self._cur.set_payload(EMPTYSTRING.join(lines)) return + # The mail composing agent should choose a unique boundary + # parameter value that does not contain the boundary parameter + # value of an enclosing multipart as a prefix. + boundary_reuse = False + for m in self._msgstack[:-1]: + if m.is_multipart(): + enclosing_boundary = m.get_boundary() + if enclosing_boundary is not None: + if boundary.startswith(enclosing_boundary): + boundary_reuse = True # Make sure a valid content type was specified per RFC 2045:6.4. if (self._cur.get('content-transfer-encoding', '8bit').lower() not in ('7bit', '8bit', 'binary')): @@ -418,6 +428,17 @@ # We've seen either the EOF or the end boundary. If we're still # capturing the preamble, we never saw the start boundary. Note # that as a defect and store the captured text as the payload. + if boundary_reuse: + defect = errors.NotUniqueBoundaryInMultipartDefect() + self.policy.handle_defect(self._cur, defect) + self._cur.set_payload(EMPTYSTRING.join(preamble)) + epilogue = [] + for line in self._input: + if line is NeedMoreData: + yield NeedMoreData + continue + self._cur.epilogue = EMPTYSTRING.join(epilogue) + return if capturing_preamble: defect = errors.StartBoundaryNotFoundDefect() self.policy.handle_defect(self._cur, defect) diff -r b730baee0877 Lib/test/test_email/test_defect_handling.py --- a/Lib/test/test_email/test_defect_handling.py Tue May 24 16:39:23 2016 -0700 +++ b/Lib/test/test_email/test_defect_handling.py Thu May 26 16:19:36 2016 +0200 @@ -52,15 +52,14 @@ --MS_Mac_OE_3071477847_720252_MIME_Part-- """) - # XXX better would be to actually detect the duplicate. - with self._raise_point(errors.StartBoundaryNotFoundDefect): + with self._raise_point(errors.NotUniqueBoundaryInMultipartDefect): msg = self._str_msg(source) if self.raise_expected: return inner = msg.get_payload(0) self.assertTrue(hasattr(inner, 'defects')) self.assertEqual(len(self.get_defects(inner)), 1) self.assertIsInstance(self.get_defects(inner)[0], - errors.StartBoundaryNotFoundDefect) + errors.NotUniqueBoundaryInMultipartDefect) def test_multipart_no_boundary(self): source = textwrap.dedent("""\ diff -r b730baee0877 Lib/test/test_email/test_email.py --- a/Lib/test/test_email/test_email.py Tue May 24 16:39:23 2016 -0700 +++ b/Lib/test/test_email/test_email.py Thu May 26 16:19:36 2016 +0200 @@ -2077,12 +2077,11 @@ # test_defect_handling def test_same_boundary_inner_outer(self): msg = self._msgobj('msg_15.txt') - # XXX We can probably eventually do better inner = msg.get_payload(0) self.assertTrue(hasattr(inner, 'defects')) self.assertEqual(len(inner.defects), 1) self.assertIsInstance(inner.defects[0], - errors.StartBoundaryNotFoundDefect) + errors.NotUniqueBoundaryInMultipartDefect) # test_defect_handling def test_multipart_no_boundary(self): @@ -3497,7 +3496,7 @@ def test_bytes_parser_on_exception_does_not_close_file(self): with openfile('msg_15.txt', 'rb') as fp: bytesParser = email.parser.BytesParser - self.assertRaises(email.errors.StartBoundaryNotFoundDefect, + self.assertRaises(email.errors.NotUniqueBoundaryInMultipartDefect, bytesParser(policy=email.policy.strict).parse, fp) self.assertFalse(fp.closed) @@ -3510,7 +3509,7 @@ def test_parser_on_exception_does_not_close_file(self): with openfile('msg_15.txt', 'r') as fp: parser = email.parser.Parser - self.assertRaises(email.errors.StartBoundaryNotFoundDefect, + self.assertRaises(email.errors.NotUniqueBoundaryInMultipartDefect, parser(policy=email.policy.strict).parse, fp) self.assertFalse(fp.closed)