1# Copyright (C) 2003-2013 Python Software Foundation 2import copy 3import operator 4import pickle 5import unittest 6import plistlib 7import os 8import datetime 9import codecs 10import binascii 11import collections 12from test import support 13from io import BytesIO 14 15from plistlib import UID 16 17ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY) 18 19# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py 20# (which using PyObjC to control the Cocoa classes for generating plists) 21TESTDATA={ 22 plistlib.FMT_XML: binascii.a2b_base64(b''' 23 PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU 24 WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO 25 IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w 26 LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YUJp 27 Z0ludDwva2V5PgoJPGludGVnZXI+OTIyMzM3MjAzNjg1NDc3NTc2NDwvaW50 28 ZWdlcj4KCTxrZXk+YUJpZ0ludDI8L2tleT4KCTxpbnRlZ2VyPjkyMjMzNzIw 29 MzY4NTQ3NzU4NTI8L2ludGVnZXI+Cgk8a2V5PmFEYXRlPC9rZXk+Cgk8ZGF0 30 ZT4yMDA0LTEwLTI2VDEwOjMzOjMzWjwvZGF0ZT4KCTxrZXk+YURpY3Q8L2tl 31 eT4KCTxkaWN0PgoJCTxrZXk+YUZhbHNlVmFsdWU8L2tleT4KCQk8ZmFsc2Uv 32 PgoJCTxrZXk+YVRydWVWYWx1ZTwva2V5PgoJCTx0cnVlLz4KCQk8a2V5PmFV 33 bmljb2RlVmFsdWU8L2tleT4KCQk8c3RyaW5nPk3DpHNzaWcsIE1hw588L3N0 34 cmluZz4KCQk8a2V5PmFub3RoZXJTdHJpbmc8L2tleT4KCQk8c3RyaW5nPiZs 35 dDtoZWxsbyAmYW1wOyAnaGknIHRoZXJlISZndDs8L3N0cmluZz4KCQk8a2V5 36 PmRlZXBlckRpY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5hPC9rZXk+CgkJ 37 CTxpbnRlZ2VyPjE3PC9pbnRlZ2VyPgoJCQk8a2V5PmI8L2tleT4KCQkJPHJl 38 YWw+MzIuNTwvcmVhbD4KCQkJPGtleT5jPC9rZXk+CgkJCTxhcnJheT4KCQkJ 39 CTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8aW50ZWdlcj4yPC9pbnRlZ2Vy 40 PgoJCQkJPHN0cmluZz50ZXh0PC9zdHJpbmc+CgkJCTwvYXJyYXk+CgkJPC9k 41 aWN0PgoJPC9kaWN0PgoJPGtleT5hRmxvYXQ8L2tleT4KCTxyZWFsPjAuNTwv 42 cmVhbD4KCTxrZXk+YUxpc3Q8L2tleT4KCTxhcnJheT4KCQk8c3RyaW5nPkE8 43 L3N0cmluZz4KCQk8c3RyaW5nPkI8L3N0cmluZz4KCQk8aW50ZWdlcj4xMjwv 44 aW50ZWdlcj4KCQk8cmVhbD4zMi41PC9yZWFsPgoJCTxhcnJheT4KCQkJPGlu 45 dGVnZXI+MTwvaW50ZWdlcj4KCQkJPGludGVnZXI+MjwvaW50ZWdlcj4KCQkJ 46 PGludGVnZXI+MzwvaW50ZWdlcj4KCQk8L2FycmF5PgoJPC9hcnJheT4KCTxr 47 ZXk+YU5lZ2F0aXZlQmlnSW50PC9rZXk+Cgk8aW50ZWdlcj4tODAwMDAwMDAw 48 MDA8L2ludGVnZXI+Cgk8a2V5PmFOZWdhdGl2ZUludDwva2V5PgoJPGludGVn 49 ZXI+LTU8L2ludGVnZXI+Cgk8a2V5PmFTdHJpbmc8L2tleT4KCTxzdHJpbmc+ 50 RG9vZGFoPC9zdHJpbmc+Cgk8a2V5PmFuRW1wdHlEaWN0PC9rZXk+Cgk8ZGlj 51 dC8+Cgk8a2V5PmFuRW1wdHlMaXN0PC9rZXk+Cgk8YXJyYXkvPgoJPGtleT5h 52 bkludDwva2V5PgoJPGludGVnZXI+NzI4PC9pbnRlZ2VyPgoJPGtleT5uZXN0 53 ZWREYXRhPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+CgkJUEd4dmRITWdiMlln 54 WW1sdVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5k 55 VzVyCgkJUGdBQkFnTThiRzkwY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJ 56 RFBHeHZkSE1nYjJZZ1ltbHVZWEo1CgkJSUdkMWJtcytBQUVDQXp4c2IzUnpJ 57 RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZaaUJpCgkJYVc1 58 aGNua2daM1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMr 59 QUFFQ0F6eHNiM1J6CgkJSUc5bUlHSnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJH 60 OTBjeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlECgkJUEd4dmRITWdiMlln 61 WW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09CgkJPC9kYXRhPgoJPC9hcnJheT4K 62 CTxrZXk+c29tZURhdGE8L2tleT4KCTxkYXRhPgoJUEdKcGJtRnllU0JuZFc1 63 clBnPT0KCTwvZGF0YT4KCTxrZXk+c29tZU1vcmVEYXRhPC9rZXk+Cgk8ZGF0 64 YT4KCVBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJ 65 RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004CgliRzkwY3lCdlppQmlhVzVo 66 Y25rZ1ozVnVhejRBQVFJRFBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytB 67 QUVDQXp4cwoJYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkw 68 Y3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHYKCWRITWdiMllnWW1s 69 dVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVy 70 UGdBQkFnTThiRzkwCgljeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlEUEd4 71 dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09Cgk8L2RhdGE+Cgk8 72 a2V5PsOFYmVucmFhPC9rZXk+Cgk8c3RyaW5nPlRoYXQgd2FzIGEgdW5pY29k 73 ZSBrZXkuPC9zdHJpbmc+CjwvZGljdD4KPC9wbGlzdD4K'''), 74 plistlib.FMT_BINARY: binascii.a2b_base64(b''' 75 YnBsaXN0MDDfEBABAgMEBQYHCAkKCwwNDg8QERITFCgpLzAxMjM0NTc2OFdh 76 QmlnSW50WGFCaWdJbnQyVWFEYXRlVWFEaWN0VmFGbG9hdFVhTGlzdF8QD2FO 77 ZWdhdGl2ZUJpZ0ludFxhTmVnYXRpdmVJbnRXYVN0cmluZ1thbkVtcHR5RGlj 78 dFthbkVtcHR5TGlzdFVhbkludFpuZXN0ZWREYXRhWHNvbWVEYXRhXHNvbWVN 79 b3JlRGF0YWcAxQBiAGUAbgByAGEAYRN/////////1BQAAAAAAAAAAIAAAAAA 80 AAAsM0GcuX30AAAA1RUWFxgZGhscHR5bYUZhbHNlVmFsdWVaYVRydWVWYWx1 81 ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGljdAgJawBN 82 AOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRoZXJlIT7T 83 HyAhIiMkUWFRYlFjEBEjQEBAAAAAAACjJSYnEAEQAlR0ZXh0Iz/gAAAAAAAA 84 pSorLCMtUUFRQhAMoyUmLhADE////+1foOAAE//////////7VkRvb2RhaNCg 85 EQLYoTZPEPo8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmlu 86 YXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBv 87 ZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs 88 b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4A 89 AQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBn 90 dW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vu 91 az5fEBdUaGF0IHdhcyBhIHVuaWNvZGUga2V5LgAIACsAMwA8AEIASABPAFUA 92 ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn 93 AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB 94 xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''), 95 'KEYED_ARCHIVE': binascii.a2b_base64(b''' 96 YnBsaXN0MDDUAQIDBAUGHB1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy 97 VCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVnB5dHlwZVYkY2xhc3NZTlMu 98 c3RyaW5nEAGAAl8QE0tleUFyY2hpdmUgVUlEIFRlc3TTEBESExQZWiRjbGFz 99 c25hbWVYJGNsYXNzZXNbJGNsYXNzaGludHNfEBdPQ19CdWlsdGluUHl0aG9u 100 VW5pY29kZaQVFhcYXxAXT0NfQnVpbHRpblB5dGhvblVuaWNvZGVfEBBPQ19Q 101 eXRob25Vbmljb2RlWE5TU3RyaW5nWE5TT2JqZWN0ohobXxAPT0NfUHl0aG9u 102 U3RyaW5nWE5TU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0R4fVHJvb3SAAQAI 103 ABEAGgAjAC0AMgA3ADsAQQBIAE8AVgBgAGIAZAB6AIEAjACVAKEAuwDAANoA 104 7QD2AP8BAgEUAR0BLwEyATcAAAAAAAACAQAAAAAAAAAgAAAAAAAAAAAAAAAA 105 AAABOQ=='''), 106} 107 108 109class TestPlistlib(unittest.TestCase): 110 111 def tearDown(self): 112 try: 113 os.unlink(support.TESTFN) 114 except: 115 pass 116 117 def _create(self, fmt=None): 118 pl = dict( 119 aString="Doodah", 120 aList=["A", "B", 12, 32.5, [1, 2, 3]], 121 aFloat = 0.5, 122 anInt = 728, 123 aBigInt = 2 ** 63 - 44, 124 aBigInt2 = 2 ** 63 + 44, 125 aNegativeInt = -5, 126 aNegativeBigInt = -80000000000, 127 aDict=dict( 128 anotherString="<hello & 'hi' there!>", 129 aUnicodeValue='M\xe4ssig, Ma\xdf', 130 aTrueValue=True, 131 aFalseValue=False, 132 deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]), 133 ), 134 someData = b"<binary gunk>", 135 someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10, 136 nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10], 137 aDate = datetime.datetime(2004, 10, 26, 10, 33, 33), 138 anEmptyDict = dict(), 139 anEmptyList = list() 140 ) 141 pl['\xc5benraa'] = "That was a unicode key." 142 return pl 143 144 def test_create(self): 145 pl = self._create() 146 self.assertEqual(pl["aString"], "Doodah") 147 self.assertEqual(pl["aDict"]["aFalseValue"], False) 148 149 def test_io(self): 150 pl = self._create() 151 with open(support.TESTFN, 'wb') as fp: 152 plistlib.dump(pl, fp) 153 154 with open(support.TESTFN, 'rb') as fp: 155 pl2 = plistlib.load(fp) 156 157 self.assertEqual(dict(pl), dict(pl2)) 158 159 self.assertRaises(AttributeError, plistlib.dump, pl, 'filename') 160 self.assertRaises(AttributeError, plistlib.load, 'filename') 161 162 def test_invalid_type(self): 163 pl = [ object() ] 164 165 for fmt in ALL_FORMATS: 166 with self.subTest(fmt=fmt): 167 self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt) 168 169 def test_invalid_uid(self): 170 with self.assertRaises(TypeError): 171 UID("not an int") 172 with self.assertRaises(ValueError): 173 UID(2 ** 64) 174 with self.assertRaises(ValueError): 175 UID(-19) 176 177 def test_int(self): 178 for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32, 179 2**63-1, 2**64-1, 1, -2**63]: 180 for fmt in ALL_FORMATS: 181 with self.subTest(pl=pl, fmt=fmt): 182 data = plistlib.dumps(pl, fmt=fmt) 183 pl2 = plistlib.loads(data) 184 self.assertIsInstance(pl2, int) 185 self.assertEqual(pl, pl2) 186 data2 = plistlib.dumps(pl2, fmt=fmt) 187 self.assertEqual(data, data2) 188 189 for fmt in ALL_FORMATS: 190 for pl in (2 ** 64 + 1, 2 ** 127-1, -2**64, -2 ** 127): 191 with self.subTest(pl=pl, fmt=fmt): 192 self.assertRaises(OverflowError, plistlib.dumps, 193 pl, fmt=fmt) 194 195 def test_bytearray(self): 196 for pl in (b'<binary gunk>', b"<lots of binary gunk>\0\1\2\3" * 10): 197 for fmt in ALL_FORMATS: 198 with self.subTest(pl=pl, fmt=fmt): 199 data = plistlib.dumps(bytearray(pl), fmt=fmt) 200 pl2 = plistlib.loads(data) 201 self.assertIsInstance(pl2, bytes) 202 self.assertEqual(pl2, pl) 203 data2 = plistlib.dumps(pl2, fmt=fmt) 204 self.assertEqual(data, data2) 205 206 def test_bytes(self): 207 pl = self._create() 208 data = plistlib.dumps(pl) 209 pl2 = plistlib.loads(data) 210 self.assertEqual(dict(pl), dict(pl2)) 211 data2 = plistlib.dumps(pl2) 212 self.assertEqual(data, data2) 213 214 def test_indentation_array(self): 215 data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]] 216 self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) 217 218 def test_indentation_dict(self): 219 data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}} 220 self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) 221 222 def test_indentation_dict_mix(self): 223 data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}} 224 self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) 225 226 def test_uid(self): 227 data = UID(1) 228 self.assertEqual(plistlib.loads(plistlib.dumps(data, fmt=plistlib.FMT_BINARY)), data) 229 dict_data = { 230 'uid0': UID(0), 231 'uid2': UID(2), 232 'uid8': UID(2 ** 8), 233 'uid16': UID(2 ** 16), 234 'uid32': UID(2 ** 32), 235 'uid63': UID(2 ** 63) 236 } 237 self.assertEqual(plistlib.loads(plistlib.dumps(dict_data, fmt=plistlib.FMT_BINARY)), dict_data) 238 239 def test_uid_data(self): 240 uid = UID(1) 241 self.assertEqual(uid.data, 1) 242 243 def test_uid_eq(self): 244 self.assertEqual(UID(1), UID(1)) 245 self.assertNotEqual(UID(1), UID(2)) 246 self.assertNotEqual(UID(1), "not uid") 247 248 def test_uid_hash(self): 249 self.assertEqual(hash(UID(1)), hash(UID(1))) 250 251 def test_uid_repr(self): 252 self.assertEqual(repr(UID(1)), "UID(1)") 253 254 def test_uid_index(self): 255 self.assertEqual(operator.index(UID(1)), 1) 256 257 def test_uid_pickle(self): 258 for proto in range(pickle.HIGHEST_PROTOCOL + 1): 259 self.assertEqual(pickle.loads(pickle.dumps(UID(19), protocol=proto)), UID(19)) 260 261 def test_uid_copy(self): 262 self.assertEqual(copy.copy(UID(1)), UID(1)) 263 self.assertEqual(copy.deepcopy(UID(1)), UID(1)) 264 265 def test_appleformatting(self): 266 for use_builtin_types in (True, False): 267 for fmt in ALL_FORMATS: 268 with self.subTest(fmt=fmt, use_builtin_types=use_builtin_types): 269 pl = plistlib.loads(TESTDATA[fmt], 270 use_builtin_types=use_builtin_types) 271 data = plistlib.dumps(pl, fmt=fmt) 272 self.assertEqual(data, TESTDATA[fmt], 273 "generated data was not identical to Apple's output") 274 275 276 def test_appleformattingfromliteral(self): 277 self.maxDiff = None 278 for fmt in ALL_FORMATS: 279 with self.subTest(fmt=fmt): 280 pl = self._create(fmt=fmt) 281 pl2 = plistlib.loads(TESTDATA[fmt], fmt=fmt) 282 self.assertEqual(dict(pl), dict(pl2), 283 "generated data was not identical to Apple's output") 284 pl2 = plistlib.loads(TESTDATA[fmt]) 285 self.assertEqual(dict(pl), dict(pl2), 286 "generated data was not identical to Apple's output") 287 288 def test_bytesio(self): 289 for fmt in ALL_FORMATS: 290 with self.subTest(fmt=fmt): 291 b = BytesIO() 292 pl = self._create(fmt=fmt) 293 plistlib.dump(pl, b, fmt=fmt) 294 pl2 = plistlib.load(BytesIO(b.getvalue()), fmt=fmt) 295 self.assertEqual(dict(pl), dict(pl2)) 296 pl2 = plistlib.load(BytesIO(b.getvalue())) 297 self.assertEqual(dict(pl), dict(pl2)) 298 299 def test_keysort_bytesio(self): 300 pl = collections.OrderedDict() 301 pl['b'] = 1 302 pl['a'] = 2 303 pl['c'] = 3 304 305 for fmt in ALL_FORMATS: 306 for sort_keys in (False, True): 307 with self.subTest(fmt=fmt, sort_keys=sort_keys): 308 b = BytesIO() 309 310 plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys) 311 pl2 = plistlib.load(BytesIO(b.getvalue()), 312 dict_type=collections.OrderedDict) 313 314 self.assertEqual(dict(pl), dict(pl2)) 315 if sort_keys: 316 self.assertEqual(list(pl2.keys()), ['a', 'b', 'c']) 317 else: 318 self.assertEqual(list(pl2.keys()), ['b', 'a', 'c']) 319 320 def test_keysort(self): 321 pl = collections.OrderedDict() 322 pl['b'] = 1 323 pl['a'] = 2 324 pl['c'] = 3 325 326 for fmt in ALL_FORMATS: 327 for sort_keys in (False, True): 328 with self.subTest(fmt=fmt, sort_keys=sort_keys): 329 data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys) 330 pl2 = plistlib.loads(data, dict_type=collections.OrderedDict) 331 332 self.assertEqual(dict(pl), dict(pl2)) 333 if sort_keys: 334 self.assertEqual(list(pl2.keys()), ['a', 'b', 'c']) 335 else: 336 self.assertEqual(list(pl2.keys()), ['b', 'a', 'c']) 337 338 def test_keys_no_string(self): 339 pl = { 42: 'aNumber' } 340 341 for fmt in ALL_FORMATS: 342 with self.subTest(fmt=fmt): 343 self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt) 344 345 b = BytesIO() 346 self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt) 347 348 def test_skipkeys(self): 349 pl = { 350 42: 'aNumber', 351 'snake': 'aWord', 352 } 353 354 for fmt in ALL_FORMATS: 355 with self.subTest(fmt=fmt): 356 data = plistlib.dumps( 357 pl, fmt=fmt, skipkeys=True, sort_keys=False) 358 359 pl2 = plistlib.loads(data) 360 self.assertEqual(pl2, {'snake': 'aWord'}) 361 362 fp = BytesIO() 363 plistlib.dump( 364 pl, fp, fmt=fmt, skipkeys=True, sort_keys=False) 365 data = fp.getvalue() 366 pl2 = plistlib.loads(fp.getvalue()) 367 self.assertEqual(pl2, {'snake': 'aWord'}) 368 369 def test_tuple_members(self): 370 pl = { 371 'first': (1, 2), 372 'second': (1, 2), 373 'third': (3, 4), 374 } 375 376 for fmt in ALL_FORMATS: 377 with self.subTest(fmt=fmt): 378 data = plistlib.dumps(pl, fmt=fmt) 379 pl2 = plistlib.loads(data) 380 self.assertEqual(pl2, { 381 'first': [1, 2], 382 'second': [1, 2], 383 'third': [3, 4], 384 }) 385 if fmt != plistlib.FMT_BINARY: 386 self.assertIsNot(pl2['first'], pl2['second']) 387 388 def test_list_members(self): 389 pl = { 390 'first': [1, 2], 391 'second': [1, 2], 392 'third': [3, 4], 393 } 394 395 for fmt in ALL_FORMATS: 396 with self.subTest(fmt=fmt): 397 data = plistlib.dumps(pl, fmt=fmt) 398 pl2 = plistlib.loads(data) 399 self.assertEqual(pl2, { 400 'first': [1, 2], 401 'second': [1, 2], 402 'third': [3, 4], 403 }) 404 self.assertIsNot(pl2['first'], pl2['second']) 405 406 def test_dict_members(self): 407 pl = { 408 'first': {'a': 1}, 409 'second': {'a': 1}, 410 'third': {'b': 2 }, 411 } 412 413 for fmt in ALL_FORMATS: 414 with self.subTest(fmt=fmt): 415 data = plistlib.dumps(pl, fmt=fmt) 416 pl2 = plistlib.loads(data) 417 self.assertEqual(pl2, { 418 'first': {'a': 1}, 419 'second': {'a': 1}, 420 'third': {'b': 2 }, 421 }) 422 self.assertIsNot(pl2['first'], pl2['second']) 423 424 def test_controlcharacters(self): 425 for i in range(128): 426 c = chr(i) 427 testString = "string containing %s" % c 428 if i >= 32 or c in "\r\n\t": 429 # \r, \n and \t are the only legal control chars in XML 430 data = plistlib.dumps(testString, fmt=plistlib.FMT_XML) 431 if c != "\r": 432 self.assertEqual(plistlib.loads(data), testString) 433 else: 434 with self.assertRaises(ValueError): 435 plistlib.dumps(testString, fmt=plistlib.FMT_XML) 436 plistlib.dumps(testString, fmt=plistlib.FMT_BINARY) 437 438 def test_non_bmp_characters(self): 439 pl = {'python': '\U0001f40d'} 440 for fmt in ALL_FORMATS: 441 with self.subTest(fmt=fmt): 442 data = plistlib.dumps(pl, fmt=fmt) 443 self.assertEqual(plistlib.loads(data), pl) 444 445 def test_lone_surrogates(self): 446 for fmt in ALL_FORMATS: 447 with self.subTest(fmt=fmt): 448 with self.assertRaises(UnicodeEncodeError): 449 plistlib.dumps('\ud8ff', fmt=fmt) 450 with self.assertRaises(UnicodeEncodeError): 451 plistlib.dumps('\udcff', fmt=fmt) 452 453 def test_nondictroot(self): 454 for fmt in ALL_FORMATS: 455 with self.subTest(fmt=fmt): 456 test1 = "abc" 457 test2 = [1, 2, 3, "abc"] 458 result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt)) 459 result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt)) 460 self.assertEqual(test1, result1) 461 self.assertEqual(test2, result2) 462 463 def test_invalidarray(self): 464 for i in ["<key>key inside an array</key>", 465 "<key>key inside an array2</key><real>3</real>", 466 "<true/><key>key inside an array3</key>"]: 467 self.assertRaises(ValueError, plistlib.loads, 468 ("<plist><array>%s</array></plist>"%i).encode()) 469 470 def test_invaliddict(self): 471 for i in ["<key><true/>k</key><string>compound key</string>", 472 "<key>single key</key>", 473 "<string>missing key</string>", 474 "<key>k1</key><string>v1</string><real>5.3</real>" 475 "<key>k1</key><key>k2</key><string>double key</string>"]: 476 self.assertRaises(ValueError, plistlib.loads, 477 ("<plist><dict>%s</dict></plist>"%i).encode()) 478 self.assertRaises(ValueError, plistlib.loads, 479 ("<plist><array><dict>%s</dict></array></plist>"%i).encode()) 480 481 def test_invalidinteger(self): 482 self.assertRaises(ValueError, plistlib.loads, 483 b"<plist><integer>not integer</integer></plist>") 484 485 def test_invalidreal(self): 486 self.assertRaises(ValueError, plistlib.loads, 487 b"<plist><integer>not real</integer></plist>") 488 489 def test_xml_encodings(self): 490 base = TESTDATA[plistlib.FMT_XML] 491 492 for xml_encoding, encoding, bom in [ 493 (b'utf-8', 'utf-8', codecs.BOM_UTF8), 494 (b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE), 495 (b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE), 496 # Expat does not support UTF-32 497 #(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE), 498 #(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE), 499 ]: 500 501 pl = self._create(fmt=plistlib.FMT_XML) 502 with self.subTest(encoding=encoding): 503 data = base.replace(b'UTF-8', xml_encoding) 504 data = bom + data.decode('utf-8').encode(encoding) 505 pl2 = plistlib.loads(data) 506 self.assertEqual(dict(pl), dict(pl2)) 507 508 def test_dump_invalid_format(self): 509 with self.assertRaises(ValueError): 510 plistlib.dumps({}, fmt="blah") 511 512 def test_load_invalid_file(self): 513 with self.assertRaises(plistlib.InvalidFileException): 514 plistlib.loads(b"these are not plist file contents") 515 516 def test_modified_uid_negative(self): 517 neg_uid = UID(1) 518 neg_uid.data = -1 # dodge the negative check in the constructor 519 with self.assertRaises(ValueError): 520 plistlib.dumps(neg_uid, fmt=plistlib.FMT_BINARY) 521 522 def test_modified_uid_huge(self): 523 huge_uid = UID(1) 524 huge_uid.data = 2 ** 64 # dodge the size check in the constructor 525 with self.assertRaises(OverflowError): 526 plistlib.dumps(huge_uid, fmt=plistlib.FMT_BINARY) 527 528 529class TestBinaryPlistlib(unittest.TestCase): 530 531 def test_nonstandard_refs_size(self): 532 # Issue #21538: Refs and offsets are 24-bit integers 533 data = (b'bplist00' 534 b'\xd1\x00\x00\x01\x00\x00\x02QaQb' 535 b'\x00\x00\x08\x00\x00\x0f\x00\x00\x11' 536 b'\x00\x00\x00\x00\x00\x00' 537 b'\x03\x03' 538 b'\x00\x00\x00\x00\x00\x00\x00\x03' 539 b'\x00\x00\x00\x00\x00\x00\x00\x00' 540 b'\x00\x00\x00\x00\x00\x00\x00\x13') 541 self.assertEqual(plistlib.loads(data), {'a': 'b'}) 542 543 def test_dump_duplicates(self): 544 # Test effectiveness of saving duplicated objects 545 for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', 546 datetime.datetime(2004, 10, 26, 10, 33, 33), 547 plistlib.Data(b'abcde'), bytearray(b'abcde'), 548 [12, 345], (12, 345), {'12': 345}): 549 with self.subTest(x=x): 550 data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY) 551 self.assertLess(len(data), 1100, repr(data)) 552 553 def test_identity(self): 554 for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', 555 datetime.datetime(2004, 10, 26, 10, 33, 33), 556 plistlib.Data(b'abcde'), bytearray(b'abcde'), 557 [12, 345], (12, 345), {'12': 345}): 558 with self.subTest(x=x): 559 data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY) 560 a, b = plistlib.loads(data) 561 if isinstance(x, tuple): 562 x = list(x) 563 self.assertEqual(a, x) 564 self.assertEqual(b, x) 565 self.assertIs(a, b) 566 567 def test_cycles(self): 568 # recursive list 569 a = [] 570 a.append(a) 571 b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) 572 self.assertIs(b[0], b) 573 # recursive tuple 574 a = ([],) 575 a[0].append(a) 576 b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) 577 self.assertIs(b[0][0], b) 578 # recursive dict 579 a = {} 580 a['x'] = a 581 b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) 582 self.assertIs(b['x'], b) 583 584 def test_large_timestamp(self): 585 # Issue #26709: 32-bit timestamp out of range 586 for ts in -2**31-1, 2**31: 587 with self.subTest(ts=ts): 588 d = (datetime.datetime.utcfromtimestamp(0) + 589 datetime.timedelta(seconds=ts)) 590 data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY) 591 self.assertEqual(plistlib.loads(data), d) 592 593 def test_invalid_binary(self): 594 for data in [ 595 # too short data 596 b'', 597 # too large offset_table_offset and nonstandard offset_size 598 b'\x00\x08' 599 b'\x00\x00\x00\x00\x00\x00\x03\x01' 600 b'\x00\x00\x00\x00\x00\x00\x00\x01' 601 b'\x00\x00\x00\x00\x00\x00\x00\x00' 602 b'\x00\x00\x00\x00\x00\x00\x00\x2a', 603 # integer overflow in offset_table_offset 604 b'\x00\x08' 605 b'\x00\x00\x00\x00\x00\x00\x01\x01' 606 b'\x00\x00\x00\x00\x00\x00\x00\x01' 607 b'\x00\x00\x00\x00\x00\x00\x00\x00' 608 b'\xff\xff\xff\xff\xff\xff\xff\xff', 609 # offset_size = 0 610 b'\x00\x08' 611 b'\x00\x00\x00\x00\x00\x00\x00\x01' 612 b'\x00\x00\x00\x00\x00\x00\x00\x01' 613 b'\x00\x00\x00\x00\x00\x00\x00\x00' 614 b'\x00\x00\x00\x00\x00\x00\x00\x09', 615 # ref_size = 0 616 b'\xa1\x01\x00\x08\x0a' 617 b'\x00\x00\x00\x00\x00\x00\x01\x00' 618 b'\x00\x00\x00\x00\x00\x00\x00\x02' 619 b'\x00\x00\x00\x00\x00\x00\x00\x00' 620 b'\x00\x00\x00\x00\x00\x00\x00\x0b', 621 # integer overflow in offset 622 b'\x00\xff\xff\xff\xff\xff\xff\xff\xff' 623 b'\x00\x00\x00\x00\x00\x00\x08\x01' 624 b'\x00\x00\x00\x00\x00\x00\x00\x01' 625 b'\x00\x00\x00\x00\x00\x00\x00\x00' 626 b'\x00\x00\x00\x00\x00\x00\x00\x09', 627 # invalid ASCII 628 b'\x51\xff\x08' 629 b'\x00\x00\x00\x00\x00\x00\x01\x01' 630 b'\x00\x00\x00\x00\x00\x00\x00\x01' 631 b'\x00\x00\x00\x00\x00\x00\x00\x00' 632 b'\x00\x00\x00\x00\x00\x00\x00\x0a', 633 # invalid UTF-16 634 b'\x61\xd8\x00\x08' 635 b'\x00\x00\x00\x00\x00\x00\x01\x01' 636 b'\x00\x00\x00\x00\x00\x00\x00\x01' 637 b'\x00\x00\x00\x00\x00\x00\x00\x00' 638 b'\x00\x00\x00\x00\x00\x00\x00\x0b', 639 ]: 640 with self.assertRaises(plistlib.InvalidFileException): 641 plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) 642 643 644class TestPlistlibDeprecated(unittest.TestCase): 645 def test_io_deprecated(self): 646 pl_in = { 647 'key': 42, 648 'sub': { 649 'key': 9, 650 'alt': 'value', 651 'data': b'buffer', 652 } 653 } 654 pl_out = { 655 'key': 42, 656 'sub': { 657 'key': 9, 658 'alt': 'value', 659 'data': plistlib.Data(b'buffer'), 660 } 661 } 662 663 self.addCleanup(support.unlink, support.TESTFN) 664 with self.assertWarns(DeprecationWarning): 665 plistlib.writePlist(pl_in, support.TESTFN) 666 667 with self.assertWarns(DeprecationWarning): 668 pl2 = plistlib.readPlist(support.TESTFN) 669 670 self.assertEqual(pl_out, pl2) 671 672 os.unlink(support.TESTFN) 673 674 with open(support.TESTFN, 'wb') as fp: 675 with self.assertWarns(DeprecationWarning): 676 plistlib.writePlist(pl_in, fp) 677 678 with open(support.TESTFN, 'rb') as fp: 679 with self.assertWarns(DeprecationWarning): 680 pl2 = plistlib.readPlist(fp) 681 682 self.assertEqual(pl_out, pl2) 683 684 def test_bytes_deprecated(self): 685 pl = { 686 'key': 42, 687 'sub': { 688 'key': 9, 689 'alt': 'value', 690 'data': b'buffer', 691 } 692 } 693 with self.assertWarns(DeprecationWarning): 694 data = plistlib.writePlistToBytes(pl) 695 696 with self.assertWarns(DeprecationWarning): 697 pl2 = plistlib.readPlistFromBytes(data) 698 699 self.assertIsInstance(pl2, dict) 700 self.assertEqual(pl2, dict( 701 key=42, 702 sub=dict( 703 key=9, 704 alt='value', 705 data=plistlib.Data(b'buffer'), 706 ) 707 )) 708 709 with self.assertWarns(DeprecationWarning): 710 data2 = plistlib.writePlistToBytes(pl2) 711 self.assertEqual(data, data2) 712 713 def test_dataobject_deprecated(self): 714 in_data = { 'key': plistlib.Data(b'hello') } 715 out_data = { 'key': b'hello' } 716 717 buf = plistlib.dumps(in_data) 718 719 cur = plistlib.loads(buf) 720 self.assertEqual(cur, out_data) 721 self.assertEqual(cur, in_data) 722 723 cur = plistlib.loads(buf, use_builtin_types=False) 724 self.assertEqual(cur, out_data) 725 self.assertEqual(cur, in_data) 726 727 with self.assertWarns(DeprecationWarning): 728 cur = plistlib.readPlistFromBytes(buf) 729 self.assertEqual(cur, out_data) 730 self.assertEqual(cur, in_data) 731 732 733class TestKeyedArchive(unittest.TestCase): 734 def test_keyed_archive_data(self): 735 # This is the structure of a NSKeyedArchive packed plist 736 data = { 737 '$version': 100000, 738 '$objects': [ 739 '$null', { 740 'pytype': 1, 741 '$class': UID(2), 742 'NS.string': 'KeyArchive UID Test' 743 }, 744 { 745 '$classname': 'OC_BuiltinPythonUnicode', 746 '$classes': [ 747 'OC_BuiltinPythonUnicode', 748 'OC_PythonUnicode', 749 'NSString', 750 'NSObject' 751 ], 752 '$classhints': [ 753 'OC_PythonString', 'NSString' 754 ] 755 } 756 ], 757 '$archiver': 'NSKeyedArchiver', 758 '$top': { 759 'root': UID(1) 760 } 761 } 762 self.assertEqual(plistlib.loads(TESTDATA["KEYED_ARCHIVE"]), data) 763 764 765class MiscTestCase(unittest.TestCase): 766 def test__all__(self): 767 blacklist = {"PlistFormat", "PLISTHEADER"} 768 support.check__all__(self, plistlib, blacklist=blacklist) 769 770 771if __name__ == '__main__': 772 unittest.main() 773