• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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