summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNed Deily <nad@acm.org>2011-05-28 09:19:19 (GMT)
committerNed Deily <nad@acm.org>2011-05-28 09:19:19 (GMT)
commitb8e59f77e65ba4caeda8d910bd66df01a468cbea (patch)
treefac7e3f59b617ce67aefd954f0fd67abf099b094
parent9a7c524dc613595e0d88b3e22b0e49b69467ffc4 (diff)
downloadcpython-b8e59f77e65ba4caeda8d910bd66df01a468cbea.zip
cpython-b8e59f77e65ba4caeda8d910bd66df01a468cbea.tar.gz
cpython-b8e59f77e65ba4caeda8d910bd66df01a468cbea.tar.bz2
Issue #985064: Make plistlib more resilient to faulty input plists.
Patch by Mher Movsisyan.
-rw-r--r--Lib/plistlib.py59
-rw-r--r--Lib/test/test_plistlib.py26
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS3
4 files changed, 67 insertions, 22 deletions
diff --git a/Lib/plistlib.py b/Lib/plistlib.py
index fbba791..fe622ad 100644
--- a/Lib/plistlib.py
+++ b/Lib/plistlib.py
@@ -68,13 +68,15 @@ def readPlist(pathOrFile):
usually is a dictionary).
"""
didOpen = False
- if isinstance(pathOrFile, str):
- pathOrFile = open(pathOrFile, 'rb')
- didOpen = True
- p = PlistParser()
- rootObject = p.parse(pathOrFile)
- if didOpen:
- pathOrFile.close()
+ try:
+ if isinstance(pathOrFile, str):
+ pathOrFile = open(pathOrFile, 'rb')
+ didOpen = True
+ p = PlistParser()
+ rootObject = p.parse(pathOrFile)
+ finally:
+ if didOpen:
+ pathOrFile.close()
return rootObject
@@ -83,15 +85,17 @@ def writePlist(rootObject, pathOrFile):
file name or a (writable) file object.
"""
didOpen = False
- if isinstance(pathOrFile, str):
- pathOrFile = open(pathOrFile, 'wb')
- didOpen = True
- writer = PlistWriter(pathOrFile)
- writer.writeln("<plist version=\"1.0\">")
- writer.writeValue(rootObject)
- writer.writeln("</plist>")
- if didOpen:
- pathOrFile.close()
+ try:
+ if isinstance(pathOrFile, str):
+ pathOrFile = open(pathOrFile, 'wb')
+ didOpen = True
+ writer = PlistWriter(pathOrFile)
+ writer.writeln("<plist version=\"1.0\">")
+ writer.writeValue(rootObject)
+ writer.writeln("</plist>")
+ finally:
+ if didOpen:
+ pathOrFile.close()
def readPlistFromBytes(data):
@@ -352,7 +356,6 @@ class Data:
def __repr__(self):
return "%s(%s)" % (self.__class__.__name__, repr(self.data))
-
class PlistParser:
def __init__(self):
@@ -362,11 +365,11 @@ class PlistParser:
def parse(self, fileobj):
from xml.parsers.expat import ParserCreate
- parser = ParserCreate()
- parser.StartElementHandler = self.handleBeginElement
- parser.EndElementHandler = self.handleEndElement
- parser.CharacterDataHandler = self.handleData
- parser.ParseFile(fileobj)
+ self.parser = ParserCreate()
+ self.parser.StartElementHandler = self.handleBeginElement
+ self.parser.EndElementHandler = self.handleEndElement
+ self.parser.CharacterDataHandler = self.handleData
+ self.parser.ParseFile(fileobj)
return self.root
def handleBeginElement(self, element, attrs):
@@ -385,12 +388,18 @@ class PlistParser:
def addObject(self, value):
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.currentKey = None
elif not self.stack:
# this is the root object
self.root = value
else:
+ if not isinstance(self.stack[-1], type([])):
+ raise ValueError("unexpected element at line %d" %
+ self.parser.CurrentLineNumber)
self.stack[-1].append(value)
def getData(self):
@@ -405,9 +414,15 @@ class PlistParser:
self.addObject(d)
self.stack.append(d)
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()
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()
def begin_array(self, attrs):
diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py
index b9a46b7..ccda920 100644
--- a/Lib/test/test_plistlib.py
+++ b/Lib/test/test_plistlib.py
@@ -175,6 +175,32 @@ class TestPlistlib(unittest.TestCase):
self.assertEqual(test1, result1)
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():
support.run_unittest(TestPlistlib)
diff --git a/Misc/ACKS b/Misc/ACKS
index 80fda10..c698d73 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -563,6 +563,7 @@ Skip Montanaro
Paul Moore
Derek Morr
James A Morrison
+Mher Movsisyan
Sjoerd Mullender
Sape Mullender
Michael Muller
diff --git a/Misc/NEWS b/Misc/NEWS
index dada5c2..ec1bbf2 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -80,6 +80,9 @@ Core and Builtins
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: FileIO.readall() now raises a ValueError instead of an IOError