mirror of
https://github.com/python/cpython.git
synced 2025-07-17 00:05:20 +00:00

Plistlib currently throws an exception when asked to decode a valid .plist file that was generated by Apple's NSKeyedArchiver. Specifically, this is caused by a byte 0x80 (signifying a UID) not being understood. This fixes the problem by enabling the binary plist reader and writer to read and write plistlib.UID objects.
756 lines
29 KiB
Python
756 lines
29 KiB
Python
# Copyright (C) 2003-2013 Python Software Foundation
|
|
import copy
|
|
import operator
|
|
import pickle
|
|
import unittest
|
|
import plistlib
|
|
import os
|
|
import datetime
|
|
import codecs
|
|
import binascii
|
|
import collections
|
|
from test import support
|
|
from io import BytesIO
|
|
|
|
from plistlib import UID
|
|
|
|
ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
|
|
|
|
# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
|
|
# (which using PyObjC to control the Cocoa classes for generating plists)
|
|
TESTDATA={
|
|
plistlib.FMT_XML: binascii.a2b_base64(b'''
|
|
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
|
|
WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
|
|
IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
|
|
LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YUJp
|
|
Z0ludDwva2V5PgoJPGludGVnZXI+OTIyMzM3MjAzNjg1NDc3NTc2NDwvaW50
|
|
ZWdlcj4KCTxrZXk+YUJpZ0ludDI8L2tleT4KCTxpbnRlZ2VyPjkyMjMzNzIw
|
|
MzY4NTQ3NzU4NTI8L2ludGVnZXI+Cgk8a2V5PmFEYXRlPC9rZXk+Cgk8ZGF0
|
|
ZT4yMDA0LTEwLTI2VDEwOjMzOjMzWjwvZGF0ZT4KCTxrZXk+YURpY3Q8L2tl
|
|
eT4KCTxkaWN0PgoJCTxrZXk+YUZhbHNlVmFsdWU8L2tleT4KCQk8ZmFsc2Uv
|
|
PgoJCTxrZXk+YVRydWVWYWx1ZTwva2V5PgoJCTx0cnVlLz4KCQk8a2V5PmFV
|
|
bmljb2RlVmFsdWU8L2tleT4KCQk8c3RyaW5nPk3DpHNzaWcsIE1hw588L3N0
|
|
cmluZz4KCQk8a2V5PmFub3RoZXJTdHJpbmc8L2tleT4KCQk8c3RyaW5nPiZs
|
|
dDtoZWxsbyAmYW1wOyAnaGknIHRoZXJlISZndDs8L3N0cmluZz4KCQk8a2V5
|
|
PmRlZXBlckRpY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5hPC9rZXk+CgkJ
|
|
CTxpbnRlZ2VyPjE3PC9pbnRlZ2VyPgoJCQk8a2V5PmI8L2tleT4KCQkJPHJl
|
|
YWw+MzIuNTwvcmVhbD4KCQkJPGtleT5jPC9rZXk+CgkJCTxhcnJheT4KCQkJ
|
|
CTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8aW50ZWdlcj4yPC9pbnRlZ2Vy
|
|
PgoJCQkJPHN0cmluZz50ZXh0PC9zdHJpbmc+CgkJCTwvYXJyYXk+CgkJPC9k
|
|
aWN0PgoJPC9kaWN0PgoJPGtleT5hRmxvYXQ8L2tleT4KCTxyZWFsPjAuNTwv
|
|
cmVhbD4KCTxrZXk+YUxpc3Q8L2tleT4KCTxhcnJheT4KCQk8c3RyaW5nPkE8
|
|
L3N0cmluZz4KCQk8c3RyaW5nPkI8L3N0cmluZz4KCQk8aW50ZWdlcj4xMjwv
|
|
aW50ZWdlcj4KCQk8cmVhbD4zMi41PC9yZWFsPgoJCTxhcnJheT4KCQkJPGlu
|
|
dGVnZXI+MTwvaW50ZWdlcj4KCQkJPGludGVnZXI+MjwvaW50ZWdlcj4KCQkJ
|
|
PGludGVnZXI+MzwvaW50ZWdlcj4KCQk8L2FycmF5PgoJPC9hcnJheT4KCTxr
|
|
ZXk+YU5lZ2F0aXZlQmlnSW50PC9rZXk+Cgk8aW50ZWdlcj4tODAwMDAwMDAw
|
|
MDA8L2ludGVnZXI+Cgk8a2V5PmFOZWdhdGl2ZUludDwva2V5PgoJPGludGVn
|
|
ZXI+LTU8L2ludGVnZXI+Cgk8a2V5PmFTdHJpbmc8L2tleT4KCTxzdHJpbmc+
|
|
RG9vZGFoPC9zdHJpbmc+Cgk8a2V5PmFuRW1wdHlEaWN0PC9rZXk+Cgk8ZGlj
|
|
dC8+Cgk8a2V5PmFuRW1wdHlMaXN0PC9rZXk+Cgk8YXJyYXkvPgoJPGtleT5h
|
|
bkludDwva2V5PgoJPGludGVnZXI+NzI4PC9pbnRlZ2VyPgoJPGtleT5uZXN0
|
|
ZWREYXRhPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+CgkJUEd4dmRITWdiMlln
|
|
WW1sdVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5k
|
|
VzVyCgkJUGdBQkFnTThiRzkwY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJ
|
|
RFBHeHZkSE1nYjJZZ1ltbHVZWEo1CgkJSUdkMWJtcytBQUVDQXp4c2IzUnpJ
|
|
RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZaaUJpCgkJYVc1
|
|
aGNua2daM1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMr
|
|
QUFFQ0F6eHNiM1J6CgkJSUc5bUlHSnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJH
|
|
OTBjeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlECgkJUEd4dmRITWdiMlln
|
|
WW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09CgkJPC9kYXRhPgoJPC9hcnJheT4K
|
|
CTxrZXk+c29tZURhdGE8L2tleT4KCTxkYXRhPgoJUEdKcGJtRnllU0JuZFc1
|
|
clBnPT0KCTwvZGF0YT4KCTxrZXk+c29tZU1vcmVEYXRhPC9rZXk+Cgk8ZGF0
|
|
YT4KCVBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJ
|
|
RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004CgliRzkwY3lCdlppQmlhVzVo
|
|
Y25rZ1ozVnVhejRBQVFJRFBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytB
|
|
QUVDQXp4cwoJYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkw
|
|
Y3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHYKCWRITWdiMllnWW1s
|
|
dVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVy
|
|
UGdBQkFnTThiRzkwCgljeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlEUEd4
|
|
dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09Cgk8L2RhdGE+Cgk8
|
|
a2V5PsOFYmVucmFhPC9rZXk+Cgk8c3RyaW5nPlRoYXQgd2FzIGEgdW5pY29k
|
|
ZSBrZXkuPC9zdHJpbmc+CjwvZGljdD4KPC9wbGlzdD4K'''),
|
|
plistlib.FMT_BINARY: binascii.a2b_base64(b'''
|
|
YnBsaXN0MDDfEBABAgMEBQYHCAkKCwwNDg8QERITFCgpLzAxMjM0NTc2OFdh
|
|
QmlnSW50WGFCaWdJbnQyVWFEYXRlVWFEaWN0VmFGbG9hdFVhTGlzdF8QD2FO
|
|
ZWdhdGl2ZUJpZ0ludFxhTmVnYXRpdmVJbnRXYVN0cmluZ1thbkVtcHR5RGlj
|
|
dFthbkVtcHR5TGlzdFVhbkludFpuZXN0ZWREYXRhWHNvbWVEYXRhXHNvbWVN
|
|
b3JlRGF0YWcAxQBiAGUAbgByAGEAYRN/////////1BQAAAAAAAAAAIAAAAAA
|
|
AAAsM0GcuX30AAAA1RUWFxgZGhscHR5bYUZhbHNlVmFsdWVaYVRydWVWYWx1
|
|
ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGljdAgJawBN
|
|
AOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRoZXJlIT7T
|
|
HyAhIiMkUWFRYlFjEBEjQEBAAAAAAACjJSYnEAEQAlR0ZXh0Iz/gAAAAAAAA
|
|
pSorLCMtUUFRQhAMoyUmLhADE////+1foOAAE//////////7VkRvb2RhaNCg
|
|
EQLYoTZPEPo8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmlu
|
|
YXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBv
|
|
ZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs
|
|
b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4A
|
|
AQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBn
|
|
dW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vu
|
|
az5fEBdUaGF0IHdhcyBhIHVuaWNvZGUga2V5LgAIACsAMwA8AEIASABPAFUA
|
|
ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn
|
|
AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB
|
|
xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''),
|
|
'KEYED_ARCHIVE': binascii.a2b_base64(b'''
|
|
YnBsaXN0MDDUAQIDBAUGHB1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy
|
|
VCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVnB5dHlwZVYkY2xhc3NZTlMu
|
|
c3RyaW5nEAGAAl8QE0tleUFyY2hpdmUgVUlEIFRlc3TTEBESExQZWiRjbGFz
|
|
c25hbWVYJGNsYXNzZXNbJGNsYXNzaGludHNfEBdPQ19CdWlsdGluUHl0aG9u
|
|
VW5pY29kZaQVFhcYXxAXT0NfQnVpbHRpblB5dGhvblVuaWNvZGVfEBBPQ19Q
|
|
eXRob25Vbmljb2RlWE5TU3RyaW5nWE5TT2JqZWN0ohobXxAPT0NfUHl0aG9u
|
|
U3RyaW5nWE5TU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0R4fVHJvb3SAAQAI
|
|
ABEAGgAjAC0AMgA3ADsAQQBIAE8AVgBgAGIAZAB6AIEAjACVAKEAuwDAANoA
|
|
7QD2AP8BAgEUAR0BLwEyATcAAAAAAAACAQAAAAAAAAAgAAAAAAAAAAAAAAAA
|
|
AAABOQ=='''),
|
|
}
|
|
|
|
|
|
class TestPlistlib(unittest.TestCase):
|
|
|
|
def tearDown(self):
|
|
try:
|
|
os.unlink(support.TESTFN)
|
|
except:
|
|
pass
|
|
|
|
def _create(self, fmt=None):
|
|
pl = dict(
|
|
aString="Doodah",
|
|
aList=["A", "B", 12, 32.5, [1, 2, 3]],
|
|
aFloat = 0.5,
|
|
anInt = 728,
|
|
aBigInt = 2 ** 63 - 44,
|
|
aBigInt2 = 2 ** 63 + 44,
|
|
aNegativeInt = -5,
|
|
aNegativeBigInt = -80000000000,
|
|
aDict=dict(
|
|
anotherString="<hello & 'hi' there!>",
|
|
aUnicodeValue='M\xe4ssig, Ma\xdf',
|
|
aTrueValue=True,
|
|
aFalseValue=False,
|
|
deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
|
|
),
|
|
someData = b"<binary gunk>",
|
|
someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10,
|
|
nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10],
|
|
aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
|
|
anEmptyDict = dict(),
|
|
anEmptyList = list()
|
|
)
|
|
pl['\xc5benraa'] = "That was a unicode key."
|
|
return pl
|
|
|
|
def test_create(self):
|
|
pl = self._create()
|
|
self.assertEqual(pl["aString"], "Doodah")
|
|
self.assertEqual(pl["aDict"]["aFalseValue"], False)
|
|
|
|
def test_io(self):
|
|
pl = self._create()
|
|
with open(support.TESTFN, 'wb') as fp:
|
|
plistlib.dump(pl, fp)
|
|
|
|
with open(support.TESTFN, 'rb') as fp:
|
|
pl2 = plistlib.load(fp)
|
|
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
|
|
self.assertRaises(AttributeError, plistlib.dump, pl, 'filename')
|
|
self.assertRaises(AttributeError, plistlib.load, 'filename')
|
|
|
|
def test_invalid_type(self):
|
|
pl = [ object() ]
|
|
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
|
|
|
|
def test_invalid_uid(self):
|
|
with self.assertRaises(TypeError):
|
|
UID("not an int")
|
|
with self.assertRaises(ValueError):
|
|
UID(2 ** 64)
|
|
with self.assertRaises(ValueError):
|
|
UID(-19)
|
|
|
|
def test_int(self):
|
|
for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32,
|
|
2**63-1, 2**64-1, 1, -2**63]:
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(pl=pl, fmt=fmt):
|
|
data = plistlib.dumps(pl, fmt=fmt)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertIsInstance(pl2, int)
|
|
self.assertEqual(pl, pl2)
|
|
data2 = plistlib.dumps(pl2, fmt=fmt)
|
|
self.assertEqual(data, data2)
|
|
|
|
for fmt in ALL_FORMATS:
|
|
for pl in (2 ** 64 + 1, 2 ** 127-1, -2**64, -2 ** 127):
|
|
with self.subTest(pl=pl, fmt=fmt):
|
|
self.assertRaises(OverflowError, plistlib.dumps,
|
|
pl, fmt=fmt)
|
|
|
|
def test_bytearray(self):
|
|
for pl in (b'<binary gunk>', b"<lots of binary gunk>\0\1\2\3" * 10):
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(pl=pl, fmt=fmt):
|
|
data = plistlib.dumps(bytearray(pl), fmt=fmt)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertIsInstance(pl2, bytes)
|
|
self.assertEqual(pl2, pl)
|
|
data2 = plistlib.dumps(pl2, fmt=fmt)
|
|
self.assertEqual(data, data2)
|
|
|
|
def test_bytes(self):
|
|
pl = self._create()
|
|
data = plistlib.dumps(pl)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
data2 = plistlib.dumps(pl2)
|
|
self.assertEqual(data, data2)
|
|
|
|
def test_indentation_array(self):
|
|
data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]]
|
|
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
|
|
|
|
def test_indentation_dict(self):
|
|
data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}}
|
|
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
|
|
|
|
def test_indentation_dict_mix(self):
|
|
data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
|
|
self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
|
|
|
|
def test_uid(self):
|
|
data = UID(1)
|
|
self.assertEqual(plistlib.loads(plistlib.dumps(data, fmt=plistlib.FMT_BINARY)), data)
|
|
dict_data = {
|
|
'uid0': UID(0),
|
|
'uid2': UID(2),
|
|
'uid8': UID(2 ** 8),
|
|
'uid16': UID(2 ** 16),
|
|
'uid32': UID(2 ** 32),
|
|
'uid63': UID(2 ** 63)
|
|
}
|
|
self.assertEqual(plistlib.loads(plistlib.dumps(dict_data, fmt=plistlib.FMT_BINARY)), dict_data)
|
|
|
|
def test_uid_data(self):
|
|
uid = UID(1)
|
|
self.assertEqual(uid.data, 1)
|
|
|
|
def test_uid_eq(self):
|
|
self.assertEqual(UID(1), UID(1))
|
|
self.assertNotEqual(UID(1), UID(2))
|
|
self.assertNotEqual(UID(1), "not uid")
|
|
|
|
def test_uid_hash(self):
|
|
self.assertEqual(hash(UID(1)), hash(UID(1)))
|
|
|
|
def test_uid_repr(self):
|
|
self.assertEqual(repr(UID(1)), "UID(1)")
|
|
|
|
def test_uid_index(self):
|
|
self.assertEqual(operator.index(UID(1)), 1)
|
|
|
|
def test_uid_pickle(self):
|
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
|
self.assertEqual(pickle.loads(pickle.dumps(UID(19), protocol=proto)), UID(19))
|
|
|
|
def test_uid_copy(self):
|
|
self.assertEqual(copy.copy(UID(1)), UID(1))
|
|
self.assertEqual(copy.deepcopy(UID(1)), UID(1))
|
|
|
|
def test_appleformatting(self):
|
|
for use_builtin_types in (True, False):
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types):
|
|
pl = plistlib.loads(TESTDATA[fmt],
|
|
use_builtin_types=use_builtin_types)
|
|
data = plistlib.dumps(pl, fmt=fmt)
|
|
self.assertEqual(data, TESTDATA[fmt],
|
|
"generated data was not identical to Apple's output")
|
|
|
|
|
|
def test_appleformattingfromliteral(self):
|
|
self.maxDiff = None
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
pl = self._create(fmt=fmt)
|
|
pl2 = plistlib.loads(TESTDATA[fmt], fmt=fmt)
|
|
self.assertEqual(dict(pl), dict(pl2),
|
|
"generated data was not identical to Apple's output")
|
|
pl2 = plistlib.loads(TESTDATA[fmt])
|
|
self.assertEqual(dict(pl), dict(pl2),
|
|
"generated data was not identical to Apple's output")
|
|
|
|
def test_bytesio(self):
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
b = BytesIO()
|
|
pl = self._create(fmt=fmt)
|
|
plistlib.dump(pl, b, fmt=fmt)
|
|
pl2 = plistlib.load(BytesIO(b.getvalue()), fmt=fmt)
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
pl2 = plistlib.load(BytesIO(b.getvalue()))
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
|
|
def test_keysort_bytesio(self):
|
|
pl = collections.OrderedDict()
|
|
pl['b'] = 1
|
|
pl['a'] = 2
|
|
pl['c'] = 3
|
|
|
|
for fmt in ALL_FORMATS:
|
|
for sort_keys in (False, True):
|
|
with self.subTest(fmt=fmt, sort_keys=sort_keys):
|
|
b = BytesIO()
|
|
|
|
plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys)
|
|
pl2 = plistlib.load(BytesIO(b.getvalue()),
|
|
dict_type=collections.OrderedDict)
|
|
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
if sort_keys:
|
|
self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
|
|
else:
|
|
self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
|
|
|
|
def test_keysort(self):
|
|
pl = collections.OrderedDict()
|
|
pl['b'] = 1
|
|
pl['a'] = 2
|
|
pl['c'] = 3
|
|
|
|
for fmt in ALL_FORMATS:
|
|
for sort_keys in (False, True):
|
|
with self.subTest(fmt=fmt, sort_keys=sort_keys):
|
|
data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys)
|
|
pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
|
|
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
if sort_keys:
|
|
self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
|
|
else:
|
|
self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
|
|
|
|
def test_keys_no_string(self):
|
|
pl = { 42: 'aNumber' }
|
|
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
|
|
|
|
b = BytesIO()
|
|
self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt)
|
|
|
|
def test_skipkeys(self):
|
|
pl = {
|
|
42: 'aNumber',
|
|
'snake': 'aWord',
|
|
}
|
|
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
data = plistlib.dumps(
|
|
pl, fmt=fmt, skipkeys=True, sort_keys=False)
|
|
|
|
pl2 = plistlib.loads(data)
|
|
self.assertEqual(pl2, {'snake': 'aWord'})
|
|
|
|
fp = BytesIO()
|
|
plistlib.dump(
|
|
pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
|
|
data = fp.getvalue()
|
|
pl2 = plistlib.loads(fp.getvalue())
|
|
self.assertEqual(pl2, {'snake': 'aWord'})
|
|
|
|
def test_tuple_members(self):
|
|
pl = {
|
|
'first': (1, 2),
|
|
'second': (1, 2),
|
|
'third': (3, 4),
|
|
}
|
|
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
data = plistlib.dumps(pl, fmt=fmt)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertEqual(pl2, {
|
|
'first': [1, 2],
|
|
'second': [1, 2],
|
|
'third': [3, 4],
|
|
})
|
|
if fmt != plistlib.FMT_BINARY:
|
|
self.assertIsNot(pl2['first'], pl2['second'])
|
|
|
|
def test_list_members(self):
|
|
pl = {
|
|
'first': [1, 2],
|
|
'second': [1, 2],
|
|
'third': [3, 4],
|
|
}
|
|
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
data = plistlib.dumps(pl, fmt=fmt)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertEqual(pl2, {
|
|
'first': [1, 2],
|
|
'second': [1, 2],
|
|
'third': [3, 4],
|
|
})
|
|
self.assertIsNot(pl2['first'], pl2['second'])
|
|
|
|
def test_dict_members(self):
|
|
pl = {
|
|
'first': {'a': 1},
|
|
'second': {'a': 1},
|
|
'third': {'b': 2 },
|
|
}
|
|
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
data = plistlib.dumps(pl, fmt=fmt)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertEqual(pl2, {
|
|
'first': {'a': 1},
|
|
'second': {'a': 1},
|
|
'third': {'b': 2 },
|
|
})
|
|
self.assertIsNot(pl2['first'], pl2['second'])
|
|
|
|
def test_controlcharacters(self):
|
|
for i in range(128):
|
|
c = chr(i)
|
|
testString = "string containing %s" % c
|
|
if i >= 32 or c in "\r\n\t":
|
|
# \r, \n and \t are the only legal control chars in XML
|
|
data = plistlib.dumps(testString, fmt=plistlib.FMT_XML)
|
|
if c != "\r":
|
|
self.assertEqual(plistlib.loads(data), testString)
|
|
else:
|
|
with self.assertRaises(ValueError):
|
|
plistlib.dumps(testString, fmt=plistlib.FMT_XML)
|
|
plistlib.dumps(testString, fmt=plistlib.FMT_BINARY)
|
|
|
|
def test_non_bmp_characters(self):
|
|
pl = {'python': '\U0001f40d'}
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
data = plistlib.dumps(pl, fmt=fmt)
|
|
self.assertEqual(plistlib.loads(data), pl)
|
|
|
|
def test_lone_surrogates(self):
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
with self.assertRaises(UnicodeEncodeError):
|
|
plistlib.dumps('\ud8ff', fmt=fmt)
|
|
with self.assertRaises(UnicodeEncodeError):
|
|
plistlib.dumps('\udcff', fmt=fmt)
|
|
|
|
def test_nondictroot(self):
|
|
for fmt in ALL_FORMATS:
|
|
with self.subTest(fmt=fmt):
|
|
test1 = "abc"
|
|
test2 = [1, 2, 3, "abc"]
|
|
result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt))
|
|
result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt))
|
|
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.loads,
|
|
("<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.loads,
|
|
("<plist><dict>%s</dict></plist>"%i).encode())
|
|
self.assertRaises(ValueError, plistlib.loads,
|
|
("<plist><array><dict>%s</dict></array></plist>"%i).encode())
|
|
|
|
def test_invalidinteger(self):
|
|
self.assertRaises(ValueError, plistlib.loads,
|
|
b"<plist><integer>not integer</integer></plist>")
|
|
|
|
def test_invalidreal(self):
|
|
self.assertRaises(ValueError, plistlib.loads,
|
|
b"<plist><integer>not real</integer></plist>")
|
|
|
|
def test_xml_encodings(self):
|
|
base = TESTDATA[plistlib.FMT_XML]
|
|
|
|
for xml_encoding, encoding, bom in [
|
|
(b'utf-8', 'utf-8', codecs.BOM_UTF8),
|
|
(b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE),
|
|
(b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE),
|
|
# Expat does not support UTF-32
|
|
#(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE),
|
|
#(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE),
|
|
]:
|
|
|
|
pl = self._create(fmt=plistlib.FMT_XML)
|
|
with self.subTest(encoding=encoding):
|
|
data = base.replace(b'UTF-8', xml_encoding)
|
|
data = bom + data.decode('utf-8').encode(encoding)
|
|
pl2 = plistlib.loads(data)
|
|
self.assertEqual(dict(pl), dict(pl2))
|
|
|
|
|
|
class TestBinaryPlistlib(unittest.TestCase):
|
|
|
|
def test_nonstandard_refs_size(self):
|
|
# Issue #21538: Refs and offsets are 24-bit integers
|
|
data = (b'bplist00'
|
|
b'\xd1\x00\x00\x01\x00\x00\x02QaQb'
|
|
b'\x00\x00\x08\x00\x00\x0f\x00\x00\x11'
|
|
b'\x00\x00\x00\x00\x00\x00'
|
|
b'\x03\x03'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x03'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x13')
|
|
self.assertEqual(plistlib.loads(data), {'a': 'b'})
|
|
|
|
def test_dump_duplicates(self):
|
|
# Test effectiveness of saving duplicated objects
|
|
for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
|
|
datetime.datetime(2004, 10, 26, 10, 33, 33),
|
|
plistlib.Data(b'abcde'), bytearray(b'abcde'),
|
|
[12, 345], (12, 345), {'12': 345}):
|
|
with self.subTest(x=x):
|
|
data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY)
|
|
self.assertLess(len(data), 1100, repr(data))
|
|
|
|
def test_identity(self):
|
|
for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
|
|
datetime.datetime(2004, 10, 26, 10, 33, 33),
|
|
plistlib.Data(b'abcde'), bytearray(b'abcde'),
|
|
[12, 345], (12, 345), {'12': 345}):
|
|
with self.subTest(x=x):
|
|
data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY)
|
|
a, b = plistlib.loads(data)
|
|
if isinstance(x, tuple):
|
|
x = list(x)
|
|
self.assertEqual(a, x)
|
|
self.assertEqual(b, x)
|
|
self.assertIs(a, b)
|
|
|
|
def test_cycles(self):
|
|
# recursive list
|
|
a = []
|
|
a.append(a)
|
|
b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
|
|
self.assertIs(b[0], b)
|
|
# recursive tuple
|
|
a = ([],)
|
|
a[0].append(a)
|
|
b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
|
|
self.assertIs(b[0][0], b)
|
|
# recursive dict
|
|
a = {}
|
|
a['x'] = a
|
|
b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
|
|
self.assertIs(b['x'], b)
|
|
|
|
def test_large_timestamp(self):
|
|
# Issue #26709: 32-bit timestamp out of range
|
|
for ts in -2**31-1, 2**31:
|
|
with self.subTest(ts=ts):
|
|
d = (datetime.datetime.utcfromtimestamp(0) +
|
|
datetime.timedelta(seconds=ts))
|
|
data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY)
|
|
self.assertEqual(plistlib.loads(data), d)
|
|
|
|
def test_invalid_binary(self):
|
|
for data in [
|
|
# too short data
|
|
b'',
|
|
# too large offset_table_offset and nonstandard offset_size
|
|
b'\x00\x08'
|
|
b'\x00\x00\x00\x00\x00\x00\x03\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x2a',
|
|
# integer overflow in offset_table_offset
|
|
b'\x00\x08'
|
|
b'\x00\x00\x00\x00\x00\x00\x01\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\xff\xff\xff\xff\xff\xff\xff\xff',
|
|
# offset_size = 0
|
|
b'\x00\x08'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x09',
|
|
# ref_size = 0
|
|
b'\xa1\x01\x00\x08\x0a'
|
|
b'\x00\x00\x00\x00\x00\x00\x01\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x02'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x0b',
|
|
# integer overflow in offset
|
|
b'\x00\xff\xff\xff\xff\xff\xff\xff\xff'
|
|
b'\x00\x00\x00\x00\x00\x00\x08\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x09',
|
|
# invalid ASCII
|
|
b'\x51\xff\x08'
|
|
b'\x00\x00\x00\x00\x00\x00\x01\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x0a',
|
|
# invalid UTF-16
|
|
b'\x61\xd8\x00\x08'
|
|
b'\x00\x00\x00\x00\x00\x00\x01\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x01'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
b'\x00\x00\x00\x00\x00\x00\x00\x0b',
|
|
]:
|
|
with self.assertRaises(plistlib.InvalidFileException):
|
|
plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
|
|
|
|
|
|
class TestPlistlibDeprecated(unittest.TestCase):
|
|
def test_io_deprecated(self):
|
|
pl_in = {
|
|
'key': 42,
|
|
'sub': {
|
|
'key': 9,
|
|
'alt': 'value',
|
|
'data': b'buffer',
|
|
}
|
|
}
|
|
pl_out = {
|
|
'key': 42,
|
|
'sub': {
|
|
'key': 9,
|
|
'alt': 'value',
|
|
'data': plistlib.Data(b'buffer'),
|
|
}
|
|
}
|
|
|
|
self.addCleanup(support.unlink, support.TESTFN)
|
|
with self.assertWarns(DeprecationWarning):
|
|
plistlib.writePlist(pl_in, support.TESTFN)
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
pl2 = plistlib.readPlist(support.TESTFN)
|
|
|
|
self.assertEqual(pl_out, pl2)
|
|
|
|
os.unlink(support.TESTFN)
|
|
|
|
with open(support.TESTFN, 'wb') as fp:
|
|
with self.assertWarns(DeprecationWarning):
|
|
plistlib.writePlist(pl_in, fp)
|
|
|
|
with open(support.TESTFN, 'rb') as fp:
|
|
with self.assertWarns(DeprecationWarning):
|
|
pl2 = plistlib.readPlist(fp)
|
|
|
|
self.assertEqual(pl_out, pl2)
|
|
|
|
def test_bytes_deprecated(self):
|
|
pl = {
|
|
'key': 42,
|
|
'sub': {
|
|
'key': 9,
|
|
'alt': 'value',
|
|
'data': b'buffer',
|
|
}
|
|
}
|
|
with self.assertWarns(DeprecationWarning):
|
|
data = plistlib.writePlistToBytes(pl)
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
pl2 = plistlib.readPlistFromBytes(data)
|
|
|
|
self.assertIsInstance(pl2, dict)
|
|
self.assertEqual(pl2, dict(
|
|
key=42,
|
|
sub=dict(
|
|
key=9,
|
|
alt='value',
|
|
data=plistlib.Data(b'buffer'),
|
|
)
|
|
))
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
data2 = plistlib.writePlistToBytes(pl2)
|
|
self.assertEqual(data, data2)
|
|
|
|
def test_dataobject_deprecated(self):
|
|
in_data = { 'key': plistlib.Data(b'hello') }
|
|
out_data = { 'key': b'hello' }
|
|
|
|
buf = plistlib.dumps(in_data)
|
|
|
|
cur = plistlib.loads(buf)
|
|
self.assertEqual(cur, out_data)
|
|
self.assertEqual(cur, in_data)
|
|
|
|
cur = plistlib.loads(buf, use_builtin_types=False)
|
|
self.assertEqual(cur, out_data)
|
|
self.assertEqual(cur, in_data)
|
|
|
|
with self.assertWarns(DeprecationWarning):
|
|
cur = plistlib.readPlistFromBytes(buf)
|
|
self.assertEqual(cur, out_data)
|
|
self.assertEqual(cur, in_data)
|
|
|
|
|
|
class TestKeyedArchive(unittest.TestCase):
|
|
def test_keyed_archive_data(self):
|
|
# This is the structure of a NSKeyedArchive packed plist
|
|
data = {
|
|
'$version': 100000,
|
|
'$objects': [
|
|
'$null', {
|
|
'pytype': 1,
|
|
'$class': UID(2),
|
|
'NS.string': 'KeyArchive UID Test'
|
|
},
|
|
{
|
|
'$classname': 'OC_BuiltinPythonUnicode',
|
|
'$classes': [
|
|
'OC_BuiltinPythonUnicode',
|
|
'OC_PythonUnicode',
|
|
'NSString',
|
|
'NSObject'
|
|
],
|
|
'$classhints': [
|
|
'OC_PythonString', 'NSString'
|
|
]
|
|
}
|
|
],
|
|
'$archiver': 'NSKeyedArchiver',
|
|
'$top': {
|
|
'root': UID(1)
|
|
}
|
|
}
|
|
self.assertEqual(plistlib.loads(TESTDATA["KEYED_ARCHIVE"]), data)
|
|
|
|
|
|
class MiscTestCase(unittest.TestCase):
|
|
def test__all__(self):
|
|
blacklist = {"PlistFormat", "PLISTHEADER"}
|
|
support.check__all__(self, plistlib, blacklist=blacklist)
|
|
|
|
|
|
def test_main():
|
|
support.run_unittest(TestPlistlib, TestPlistlibDeprecated, TestKeyedArchive, MiscTestCase)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test_main()
|