Skip to content

Commit 7ef3ff3

Browse files
committed
#12515: email now registers a defect if the MIME end boundary is missing.
This commit also restores the news item for 167256 that it looks like Terry inadvertently deleted. (Either that, or I don't understand now merging works...which is equally possible.)
1 parent d0a0e8e commit 7ef3ff3

5 files changed

Lines changed: 59 additions & 3 deletions

File tree

Doc/library/email.errors.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ this class is *not* an exception!
7373
* :class:`StartBoundaryNotFoundDefect` -- The start boundary claimed in the
7474
:mailheader:`Content-Type` header was never found.
7575

76+
* :class:`CloseBoundaryNotFoundDefect` -- A start boundary was found, but
77+
no corresponding close boundary was ever found.
78+
79+
.. versionadded: 3.3
80+
7681
* :class:`FirstHeaderLineIsContinuationDefect` -- The message had a continuation
7782
line as its first header line.
7883

Lib/email/errors.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class NoBoundaryInMultipartDefect(MessageDefect):
4242
class StartBoundaryNotFoundDefect(MessageDefect):
4343
"""The claimed start boundary was never found."""
4444

45+
class CloseBoundaryNotFoundDefect(MessageDefect):
46+
"""A start boundary was found, but not the corresponding close boundary."""
47+
4548
class FirstHeaderLineIsContinuationDefect(MessageDefect):
4649
"""A message had a continuation line as its first header line."""
4750

Lib/email/feedparser.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ def _parsegen(self):
324324
capturing_preamble = True
325325
preamble = []
326326
linesep = False
327+
close_boundary_seen = False
327328
while True:
328329
line = self._input.readline()
329330
if line is NeedMoreData:
@@ -338,6 +339,7 @@ def _parsegen(self):
338339
# the closing boundary, then we need to initialize the
339340
# epilogue with the empty string (see below).
340341
if mo.group('end'):
342+
close_boundary_seen = True
341343
linesep = mo.group('linesep')
342344
break
343345
# We saw an inter-part boundary. Were we in the preamble?
@@ -406,7 +408,6 @@ def _parsegen(self):
406408
# We've seen either the EOF or the end boundary. If we're still
407409
# capturing the preamble, we never saw the start boundary. Note
408410
# that as a defect and store the captured text as the payload.
409-
# Everything from here to the EOF is epilogue.
410411
if capturing_preamble:
411412
defect = errors.StartBoundaryNotFoundDefect()
412413
self.policy.handle_defect(self._cur, defect)
@@ -418,8 +419,15 @@ def _parsegen(self):
418419
continue
419420
self._cur.epilogue = EMPTYSTRING.join(epilogue)
420421
return
421-
# If the end boundary ended in a newline, we'll need to make sure
422-
# the epilogue isn't None
422+
# If we're not processing the preamble, then we might have seen
423+
# EOF without seeing that end boundary...that is also a defect.
424+
if not close_boundary_seen:
425+
defect = errors.CloseBoundaryNotFoundDefect()
426+
self.policy.handle_defect(self._cur, defect)
427+
return
428+
# Everything from here to the EOF is epilogue. If the end boundary
429+
# ended in a newline, we'll need to make sure the epilogue isn't
430+
# None
423431
if linesep:
424432
epilogue = ['']
425433
else:

Lib/test/test_email/test_defect_handling.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,39 @@ def test_invalid_chars_in_base64_payload_raise_on_defect(self):
278278
with self.assertRaises(errors.InvalidBase64CharactersDefect):
279279
msg.get_payload(decode=True)
280280

281+
missing_ending_boundary = textwrap.dedent("""\
282+
To: 1@harrydomain4.com
283+
Subject: Fwd: 1
284+
MIME-Version: 1.0
285+
Content-Type: multipart/alternative;
286+
boundary="------------000101020201080900040301"
287+
288+
--------------000101020201080900040301
289+
Content-Type: text/plain; charset=ISO-8859-1
290+
Content-Transfer-Encoding: 7bit
291+
292+
Alternative 1
293+
294+
--------------000101020201080900040301
295+
Content-Type: text/html; charset=ISO-8859-1
296+
Content-Transfer-Encoding: 7bit
297+
298+
Alternative 2
299+
300+
""")
301+
302+
def test_missing_ending_boundary(self):
303+
msg = self._str_msg(self.missing_ending_boundary)
304+
self.assertEqual(len(msg.get_payload()), 2)
305+
self.assertEqual(msg.get_payload(1).get_payload(), 'Alternative 2\n')
306+
self.assertDefectsEqual(self.get_defects(msg),
307+
[errors.CloseBoundaryNotFoundDefect])
308+
309+
def test_missing_ending_boundary_raise_on_defect(self):
310+
with self.assertRaises(errors.CloseBoundaryNotFoundDefect):
311+
self._str_msg(self.missing_ending_boundary,
312+
policy=self.policy.clone(raise_on_defect=True))
313+
281314

282315
class TestMessageDefectDetection(TestMessageDefectDetectionBase, TestEmailBase):
283316

Misc/NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,16 @@ Core and Builtins
4949
Library
5050
-------
5151

52+
- Issue #12515: email now registers a defect if it gets to EOF while parsing
53+
a MIME part without seeing the closing MIME boundary.
54+
5255
- Issue12510: Attempting to get invalid tooltip no longer closes Idle.
5356
Original patch by Roger Serwy.
5457

58+
- Issue #1672568: email now always decodes base64 payloads, adding padding and
59+
ignoring non-base64-alphabet characters if needed, and registering defects
60+
for any such problems.
61+
5562
- Issue #14925: email now registers a defect when the parser decides that there
5663
is a missing header/body separator line. MalformedHeaderDefect, which the
5764
existing code would never actually generate, is deprecated.

0 commit comments

Comments
 (0)