Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Lib/email/_header_value_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ def value(self):

@property
def all_defects(self):
return sum((x.all_defects for x in self), self.defects)
defects = list(self.defects)
for x in self:
defects.extend(x.all_defects)
return defects

def startswith_fws(self):
return self[0].startswith_fws()
Expand Down Expand Up @@ -1859,8 +1862,10 @@ def get_invalid_mailbox(value, endchars):
invalid_mailbox = InvalidMailbox()
while value and value[0] not in endchars:
if value[0] in PHRASE_ENDS:
invalid_mailbox.append(ValueTerminal(value[0],
'misplaced-special'))
special = ValueTerminal(value[0], 'misplaced-special')
special.defects.append(errors.InvalidHeaderDefect(
"misplaced special character {!r}".format(value[0])))
invalid_mailbox.append(special)
value = value[1:]
else:
token, value = get_phrase(value)
Expand Down
48 changes: 47 additions & 1 deletion Lib/test/test_email/test__header_value_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ def test_EWWhiteSpaceTerminal(self):
self.assertEqual(x.value, '')
self.assertEqual(x.token_type, 'fws')

def test_all_defects_collects_subtree_in_order(self):
leaf = parser.ValueTerminal('x', 'atext')
leaf.defects.append(errors.InvalidHeaderDefect('leaf'))
inner = parser.TokenList([leaf])
inner.defects.append(errors.InvalidHeaderDefect('inner'))
outer = parser.TokenList([inner])
outer.defects.append(errors.InvalidHeaderDefect('outer'))
self.assertEqual([str(d) for d in outer.all_defects],
['outer', 'inner', 'leaf'])

def test_all_defects_is_a_copy(self):
tl = parser.TokenList()
tl.defects.append(errors.InvalidHeaderDefect('only'))
defects = tl.all_defects
self.assertEqual([str(d) for d in defects], ['only'])
self.assertIsNot(defects, tl.defects)
defects.append(errors.InvalidHeaderDefect('added'))
self.assertEqual(len(tl.defects), 1)


class TestParserMixin:

Expand Down Expand Up @@ -2158,6 +2177,9 @@ def test_get_mailbox_list_unparseable_mailbox_null(self):
' "Fred Flintstone" <dinsdale@test. example.com>'),
[errors.InvalidHeaderDefect, # the 'extra' text after the local part
errors.InvalidHeaderDefect, # the local part with no angle-addr
errors.InvalidHeaderDefect, # misplaced '[' in the extra text
errors.InvalidHeaderDefect, # misplaced ']' in the extra text
errors.InvalidHeaderDefect, # misplaced '@' in the extra text
errors.ObsoleteHeaderDefect, # period in extra text (example.com)
errors.ObsoleteHeaderDefect], # (bird) in valid address.
'')
Expand Down Expand Up @@ -2186,7 +2208,9 @@ def test_get_mailbox_list_junk_after_valid_address(self):
' "Fred Flintstone" <dinsdale@test.example.com>'),
('"Roy A. Bear" <dinsdale@example.com>@@,'
' "Fred Flintstone" <dinsdale@test.example.com>'),
[errors.InvalidHeaderDefect],
[errors.InvalidHeaderDefect, # the junk after the valid address
errors.InvalidHeaderDefect, # first misplaced '@' in the junk
errors.InvalidHeaderDefect], # second misplaced '@' in the junk
'')
self.assertEqual(len(mailbox_list.mailboxes), 1)
self.assertEqual(len(mailbox_list.all_mailboxes), 2)
Expand Down Expand Up @@ -2688,6 +2712,8 @@ def test_get_address_list_mailboxes_invalid_addresses(self):
[errors.InvalidHeaderDefect, # invalid address in list
errors.InvalidHeaderDefect, # 'Foo x' local part invalid.
errors.InvalidHeaderDefect, # Missing . in 'Foo x' local part
errors.InvalidHeaderDefect, # misplaced '[' after the addr-spec
errors.InvalidHeaderDefect, # misplaced ']' after the addr-spec
errors.ObsoleteHeaderDefect, # period in 'Is.' disp-name phrase
errors.InvalidHeaderDefect, # no domain part in addr-spec
errors.ObsoleteHeaderDefect], # addr-spec has comment in it
Expand All @@ -2712,6 +2738,26 @@ def test_get_address_list_mailboxes_invalid_addresses(self):
address_list.addresses[3].all_mailboxes[0].display_name,
"Nobody Is. Special")

def test_get_address_list_misplaced_special_has_defect(self):
address_list = self._test_get_x(parser.get_address_list,
'abc@xyz.c:om',
'abc@xyz.c:om',
'abc@xyz.c:om',
[errors.InvalidHeaderDefect, # invalid address in list
errors.InvalidHeaderDefect], # the misplaced ':'
'')
invalid_mailbox = address_list.addresses[1].all_mailboxes[0]
self.assertEqual(invalid_mailbox.token_type, 'invalid-mailbox')
misplaced = invalid_mailbox[0]
self.assertEqual(misplaced.token_type, 'misplaced-special')
self.assertEqual(misplaced, ':')
# The defect is attached to the misplaced-special token itself, so the
# offending character can be located in the parse tree.
self.assertDefectsEqual(misplaced.defects,
[errors.InvalidHeaderDefect])
self.assertEqual(str(misplaced.defects[0]),
"misplaced special character ':'")

def test_get_address_list_group_empty(self):
address_list = self._test_get_x(parser.get_address_list,
'Monty Python: ;',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The :mod:`email` header parser now attaches an ``InvalidHeaderDefect`` to the
``misplaced-special`` token it creates for a stray special character in an
invalid mailbox, instead of reporting the problem only on the enclosing
address list or mailbox list. As a result,
:attr:`~email.headerregistry.BaseHeader.defects` for a malformed address
header such as ``To: abc@xyz.c:om`` gains one entry per misplaced character.
Patch by Dmitry Voropaev.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Parsing a malformed :mod:`email` address header no longer takes time
quadratic in the number of defects the parser reports. Collecting the
defects of a parse tree copied the accumulated list once per node; it now
appends to a single list. Patch by Dmitry Voropaev.
Loading