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: 9 additions & 2 deletions Lib/plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
import os
import re
import struct
from xml.parsers.expat import ParserCreate
from xml.parsers.expat import ExpatError, ParserCreate


PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
Expand Down Expand Up @@ -185,7 +185,14 @@ def parse(self, fileobj):
self.parser.EndElementHandler = self.handle_end_element
self.parser.CharacterDataHandler = self.handle_data
self.parser.EntityDeclHandler = self.handle_entity_decl
self.parser.ParseFile(fileobj)
try:
self.parser.ParseFile(fileobj)
except ExpatError as e:
raise InvalidFileException(str(e)) from e
except LookupError as e:
# An <?xml ... ?> declaration naming an encoding that Python's
# codec registry does not know raises LookupError from expat.
raise InvalidFileException(str(e)) from e
Comment on lines +192 to +195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict translation to the codec lookup failure

When a caller supplies a custom dict_type whose constructor or __setitem__ raises KeyError or IndexError, the exception propagates through ParseFile() and is converted here because both exceptions subclass LookupError; a custom file object's read() can be affected similarly. Consequently, even valid XML now raises a misleading InvalidFileException and hides an error originating in caller-provided code, unlike the binary parser and the behavior before this change. Only the plain codec-registry LookupError produced for an unknown declared encoding should be translated.

Useful? React with 👍 / 👎.

return self.root

def handle_entity_decl(self, entity_name, is_parameter_entity, value, base, system_id, public_id, notation_name):
Expand Down
14 changes: 14 additions & 0 deletions Lib/test/test_plistlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,20 @@ def test_load_invalid_file(self):
with self.assertRaises(plistlib.InvalidFileException):
plistlib.loads(b"these are not plist file contents")

def test_xml_plist_not_well_formed(self):
# gh-155397: a not-well-formed XML plist must raise the documented
# InvalidFileException, not a raw xml.parsers.expat.ExpatError.
with self.assertRaises(plistlib.InvalidFileException):
plistlib.loads(b"<plist><foo></bar></plist>", fmt=plistlib.FMT_XML)

def test_xml_plist_unknown_encoding(self):
# gh-155397: an <?xml ... ?> declaration naming an encoding that
# Python does not know must raise the documented InvalidFileException,
# not a raw LookupError.
data = b'<?xml version="1.0" encoding="BogusEncoding"?><plist></plist>'
with self.assertRaises(plistlib.InvalidFileException):
plistlib.loads(data, fmt=plistlib.FMT_XML)

def test_modified_uid_negative(self):
neg_uid = UID(1)
neg_uid.data = -1 # dodge the negative check in the constructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`plistlib.load` and :func:`plistlib.loads` now raise :exc:`plistlib.InvalidFileException` for XML plists that are not well-formed or that declare an unknown encoding, instead of leaking the underlying :exc:`~xml.parsers.expat.ExpatError` or :exc:`LookupError`.
Loading