Commit b8e59f77 authored by Ned Deily's avatar Ned Deily

Issue #985064: Make plistlib more resilient to faulty input plists.

Patch by Mher Movsisyan.
parent 9a7c524d
...@@ -68,13 +68,15 @@ def readPlist(pathOrFile): ...@@ -68,13 +68,15 @@ def readPlist(pathOrFile):
usually is a dictionary). usually is a dictionary).
""" """
didOpen = False didOpen = False
if isinstance(pathOrFile, str): try:
pathOrFile = open(pathOrFile, 'rb') if isinstance(pathOrFile, str):
didOpen = True pathOrFile = open(pathOrFile, 'rb')
p = PlistParser() didOpen = True
rootObject = p.parse(pathOrFile) p = PlistParser()
if didOpen: rootObject = p.parse(pathOrFile)
pathOrFile.close() finally:
if didOpen:
pathOrFile.close()
return rootObject return rootObject
...@@ -83,15 +85,17 @@ def writePlist(rootObject, pathOrFile): ...@@ -83,15 +85,17 @@ def writePlist(rootObject, pathOrFile):
file name or a (writable) file object. file name or a (writable) file object.
""" """
didOpen = False didOpen = False
if isinstance(pathOrFile, str): try:
pathOrFile = open(pathOrFile, 'wb') if isinstance(pathOrFile, str):
didOpen = True pathOrFile = open(pathOrFile, 'wb')
writer = PlistWriter(pathOrFile) didOpen = True
writer.writeln("<plist version=\"1.0\">") writer = PlistWriter(pathOrFile)
writer.writeValue(rootObject) writer.writeln("<plist version=\"1.0\">")
writer.writeln("</plist>") writer.writeValue(rootObject)
if didOpen: writer.writeln("</plist>")
pathOrFile.close() finally:
if didOpen:
pathOrFile.close()
def readPlistFromBytes(data): def readPlistFromBytes(data):
...@@ -352,7 +356,6 @@ class Data: ...@@ -352,7 +356,6 @@ class Data:
def __repr__(self): def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self.data)) return "%s(%s)" % (self.__class__.__name__, repr(self.data))
class PlistParser: class PlistParser:
def __init__(self): def __init__(self):
...@@ -362,11 +365,11 @@ class PlistParser: ...@@ -362,11 +365,11 @@ class PlistParser:
def parse(self, fileobj): def parse(self, fileobj):
from xml.parsers.expat import ParserCreate from xml.parsers.expat import ParserCreate
parser = ParserCreate() self.parser = ParserCreate()
parser.StartElementHandler = self.handleBeginElement self.parser.StartElementHandler = self.handleBeginElement
parser.EndElementHandler = self.handleEndElement self.parser.EndElementHandler = self.handleEndElement
parser.CharacterDataHandler = self.handleData self.parser.CharacterDataHandler = self.handleData
parser.ParseFile(fileobj) self.parser.ParseFile(fileobj)
return self.root return self.root
def handleBeginElement(self, element, attrs): def handleBeginElement(self, element, attrs):
...@@ -385,12 +388,18 @@ class PlistParser: ...@@ -385,12 +388,18 @@ class PlistParser:
def addObject(self, value): def addObject(self, value):
if self.currentKey is not None: if self.currentKey is not None:
if not isinstance(self.stack[-1], type({})):
raise ValueError("unexpected element at line %d" %
self.parser.CurrentLineNumber)
self.stack[-1][self.currentKey] = value self.stack[-1][self.currentKey] = value
self.currentKey = None self.currentKey = None
elif not self.stack: elif not self.stack:
# this is the root object # this is the root object
self.root = value self.root = value
else: else:
if not isinstance(self.stack[-1], type([])):
raise ValueError("unexpected element at line %d" %
self.parser.CurrentLineNumber)
self.stack[-1].append(value) self.stack[-1].append(value)
def getData(self): def getData(self):
...@@ -405,9 +414,15 @@ class PlistParser: ...@@ -405,9 +414,15 @@ class PlistParser:
self.addObject(d) self.addObject(d)
self.stack.append(d) self.stack.append(d)
def end_dict(self): def end_dict(self):
if self.currentKey:
raise ValueError("missing value for key '%s' at line %d" %
(self.currentKey,self.parser.CurrentLineNumber))
self.stack.pop() self.stack.pop()
def end_key(self): def end_key(self):
if self.currentKey or not isinstance(self.stack[-1], type({})):
raise ValueError("unexpected key at line %d" %
self.parser.CurrentLineNumber)
self.currentKey = self.getData() self.currentKey = self.getData()
def begin_array(self, attrs): def begin_array(self, attrs):
......
...@@ -175,6 +175,32 @@ class TestPlistlib(unittest.TestCase): ...@@ -175,6 +175,32 @@ class TestPlistlib(unittest.TestCase):
self.assertEqual(test1, result1) self.assertEqual(test1, result1)
self.assertEqual(test2, result2) self.assertEqual(test2, result2)
def test_invalidarray(self):
for i in ["<key>key inside an array</key>",
"<key>key inside an array2</key><real>3</real>",
"<true/><key>key inside an array3</key>"]:
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
("<plist><array>%s</array></plist>"%i).encode())
def test_invaliddict(self):
for i in ["<key><true/>k</key><string>compound key</string>",
"<key>single key</key>",
"<string>missing key</string>",
"<key>k1</key><string>v1</string><real>5.3</real>"
"<key>k1</key><key>k2</key><string>double key</string>"]:
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
("<plist><dict>%s</dict></plist>"%i).encode())
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
("<plist><array><dict>%s</dict></array></plist>"%i).encode())
def test_invalidinteger(self):
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
b"<plist><integer>not integer</integer></plist>")
def test_invalidreal(self):
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
b"<plist><integer>not real</integer></plist>")
def test_main(): def test_main():
support.run_unittest(TestPlistlib) support.run_unittest(TestPlistlib)
......
...@@ -563,6 +563,7 @@ Skip Montanaro ...@@ -563,6 +563,7 @@ Skip Montanaro
Paul Moore Paul Moore
Derek Morr Derek Morr
James A Morrison James A Morrison
Mher Movsisyan
Sjoerd Mullender Sjoerd Mullender
Sape Mullender Sape Mullender
Michael Muller Michael Muller
......
...@@ -80,6 +80,9 @@ Core and Builtins ...@@ -80,6 +80,9 @@ Core and Builtins
Library Library
------- -------
- Issue #985064: Make plistlib more resilient to faulty input plists.
Patch by Mher Movsisyan.
- Issue #12175: RawIOBase.readall() now returns None if read() returns None. - Issue #12175: RawIOBase.readall() now returns None if read() returns None.
- Issue #12175: FileIO.readall() now raises a ValueError instead of an IOError - Issue #12175: FileIO.readall() now raises a ValueError instead of an IOError
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment