Skip to content

Commit 59a3338

Browse files
committed
Issue #985064: Make plistlib more resilient to faulty input plists.
Patch by Mher Movsisyan.
2 parents 61be422 + 32b5cb0 commit 59a3338

4 files changed

Lines changed: 67 additions & 22 deletions

File tree

Lib/plistlib.py

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ def readPlist(pathOrFile):
6868
usually is a dictionary).
6969
"""
7070
didOpen = False
71-
if isinstance(pathOrFile, str):
72-
pathOrFile = open(pathOrFile, 'rb')
73-
didOpen = True
74-
p = PlistParser()
75-
rootObject = p.parse(pathOrFile)
76-
if didOpen:
77-
pathOrFile.close()
71+
try:
72+
if isinstance(pathOrFile, str):
73+
pathOrFile = open(pathOrFile, 'rb')
74+
didOpen = True
75+
p = PlistParser()
76+
rootObject = p.parse(pathOrFile)
77+
finally:
78+
if didOpen:
79+
pathOrFile.close()
7880
return rootObject
7981

8082

@@ -83,15 +85,17 @@ def writePlist(rootObject, pathOrFile):
8385
file name or a (writable) file object.
8486
"""
8587
didOpen = False
86-
if isinstance(pathOrFile, str):
87-
pathOrFile = open(pathOrFile, 'wb')
88-
didOpen = True
89-
writer = PlistWriter(pathOrFile)
90-
writer.writeln("<plist version=\"1.0\">")
91-
writer.writeValue(rootObject)
92-
writer.writeln("</plist>")
93-
if didOpen:
94-
pathOrFile.close()
88+
try:
89+
if isinstance(pathOrFile, str):
90+
pathOrFile = open(pathOrFile, 'wb')
91+
didOpen = True
92+
writer = PlistWriter(pathOrFile)
93+
writer.writeln("<plist version=\"1.0\">")
94+
writer.writeValue(rootObject)
95+
writer.writeln("</plist>")
96+
finally:
97+
if didOpen:
98+
pathOrFile.close()
9599

96100

97101
def readPlistFromBytes(data):
@@ -352,7 +356,6 @@ def __eq__(self, other):
352356
def __repr__(self):
353357
return "%s(%s)" % (self.__class__.__name__, repr(self.data))
354358

355-
356359
class PlistParser:
357360

358361
def __init__(self):
@@ -362,11 +365,11 @@ def __init__(self):
362365

363366
def parse(self, fileobj):
364367
from xml.parsers.expat import ParserCreate
365-
parser = ParserCreate()
366-
parser.StartElementHandler = self.handleBeginElement
367-
parser.EndElementHandler = self.handleEndElement
368-
parser.CharacterDataHandler = self.handleData
369-
parser.ParseFile(fileobj)
368+
self.parser = ParserCreate()
369+
self.parser.StartElementHandler = self.handleBeginElement
370+
self.parser.EndElementHandler = self.handleEndElement
371+
self.parser.CharacterDataHandler = self.handleData
372+
self.parser.ParseFile(fileobj)
370373
return self.root
371374

372375
def handleBeginElement(self, element, attrs):
@@ -385,12 +388,18 @@ def handleData(self, data):
385388

386389
def addObject(self, value):
387390
if self.currentKey is not None:
391+
if not isinstance(self.stack[-1], type({})):
392+
raise ValueError("unexpected element at line %d" %
393+
self.parser.CurrentLineNumber)
388394
self.stack[-1][self.currentKey] = value
389395
self.currentKey = None
390396
elif not self.stack:
391397
# this is the root object
392398
self.root = value
393399
else:
400+
if not isinstance(self.stack[-1], type([])):
401+
raise ValueError("unexpected element at line %d" %
402+
self.parser.CurrentLineNumber)
394403
self.stack[-1].append(value)
395404

396405
def getData(self):
@@ -405,9 +414,15 @@ def begin_dict(self, attrs):
405414
self.addObject(d)
406415
self.stack.append(d)
407416
def end_dict(self):
417+
if self.currentKey:
418+
raise ValueError("missing value for key '%s' at line %d" %
419+
(self.currentKey,self.parser.CurrentLineNumber))
408420
self.stack.pop()
409421

410422
def end_key(self):
423+
if self.currentKey or not isinstance(self.stack[-1], type({})):
424+
raise ValueError("unexpected key at line %d" %
425+
self.parser.CurrentLineNumber)
411426
self.currentKey = self.getData()
412427

413428
def begin_array(self, attrs):

Lib/test/test_plistlib.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,32 @@ def test_nondictroot(self):
175175
self.assertEqual(test1, result1)
176176
self.assertEqual(test2, result2)
177177

178+
def test_invalidarray(self):
179+
for i in ["<key>key inside an array</key>",
180+
"<key>key inside an array2</key><real>3</real>",
181+
"<true/><key>key inside an array3</key>"]:
182+
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
183+
("<plist><array>%s</array></plist>"%i).encode())
184+
185+
def test_invaliddict(self):
186+
for i in ["<key><true/>k</key><string>compound key</string>",
187+
"<key>single key</key>",
188+
"<string>missing key</string>",
189+
"<key>k1</key><string>v1</string><real>5.3</real>"
190+
"<key>k1</key><key>k2</key><string>double key</string>"]:
191+
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
192+
("<plist><dict>%s</dict></plist>"%i).encode())
193+
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
194+
("<plist><array><dict>%s</dict></array></plist>"%i).encode())
195+
196+
def test_invalidinteger(self):
197+
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
198+
b"<plist><integer>not integer</integer></plist>")
199+
200+
def test_invalidreal(self):
201+
self.assertRaises(ValueError, plistlib.readPlistFromBytes,
202+
b"<plist><integer>not real</integer></plist>")
203+
178204

179205
def test_main():
180206
support.run_unittest(TestPlistlib)

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,7 @@ Paul Moore
615615
Derek Morr
616616
James A Morrison
617617
Pablo Mouzo
618+
Mher Movsisyan
618619
Sjoerd Mullender
619620
Sape Mullender
620621
Michael Muller

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ Core and Builtins
172172
Library
173173
-------
174174

175+
- Issue #985064: Make plistlib more resilient to faulty input plists.
176+
Patch by Mher Movsisyan.
177+
175178
- Issue #1625: BZ2File and bz2.decompress() now support multi-stream files.
176179
Initial patch by Nir Aides.
177180

0 commit comments

Comments
 (0)