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