• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2003-2013 Python Software Foundation
2import copy
3import operator
4import pickle
5import struct
6import unittest
7import plistlib
8import os
9import datetime
10import codecs
11import binascii
12import collections
13from test import support
14from io import BytesIO
15
16from plistlib import UID
17
18ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY)
19
20# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py
21# (which using PyObjC to control the Cocoa classes for generating plists)
22TESTDATA={
23    plistlib.FMT_XML: binascii.a2b_base64(b'''
24        PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU
25        WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO
26        IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w
27        LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YUJp
28        Z0ludDwva2V5PgoJPGludGVnZXI+OTIyMzM3MjAzNjg1NDc3NTc2NDwvaW50
29        ZWdlcj4KCTxrZXk+YUJpZ0ludDI8L2tleT4KCTxpbnRlZ2VyPjkyMjMzNzIw
30        MzY4NTQ3NzU4NTI8L2ludGVnZXI+Cgk8a2V5PmFEYXRlPC9rZXk+Cgk8ZGF0
31        ZT4yMDA0LTEwLTI2VDEwOjMzOjMzWjwvZGF0ZT4KCTxrZXk+YURpY3Q8L2tl
32        eT4KCTxkaWN0PgoJCTxrZXk+YUZhbHNlVmFsdWU8L2tleT4KCQk8ZmFsc2Uv
33        PgoJCTxrZXk+YVRydWVWYWx1ZTwva2V5PgoJCTx0cnVlLz4KCQk8a2V5PmFV
34        bmljb2RlVmFsdWU8L2tleT4KCQk8c3RyaW5nPk3DpHNzaWcsIE1hw588L3N0
35        cmluZz4KCQk8a2V5PmFub3RoZXJTdHJpbmc8L2tleT4KCQk8c3RyaW5nPiZs
36        dDtoZWxsbyAmYW1wOyAnaGknIHRoZXJlISZndDs8L3N0cmluZz4KCQk8a2V5
37        PmRlZXBlckRpY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5hPC9rZXk+CgkJ
38        CTxpbnRlZ2VyPjE3PC9pbnRlZ2VyPgoJCQk8a2V5PmI8L2tleT4KCQkJPHJl
39        YWw+MzIuNTwvcmVhbD4KCQkJPGtleT5jPC9rZXk+CgkJCTxhcnJheT4KCQkJ
40        CTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8aW50ZWdlcj4yPC9pbnRlZ2Vy
41        PgoJCQkJPHN0cmluZz50ZXh0PC9zdHJpbmc+CgkJCTwvYXJyYXk+CgkJPC9k
42        aWN0PgoJPC9kaWN0PgoJPGtleT5hRmxvYXQ8L2tleT4KCTxyZWFsPjAuNTwv
43        cmVhbD4KCTxrZXk+YUxpc3Q8L2tleT4KCTxhcnJheT4KCQk8c3RyaW5nPkE8
44        L3N0cmluZz4KCQk8c3RyaW5nPkI8L3N0cmluZz4KCQk8aW50ZWdlcj4xMjwv
45        aW50ZWdlcj4KCQk8cmVhbD4zMi41PC9yZWFsPgoJCTxhcnJheT4KCQkJPGlu
46        dGVnZXI+MTwvaW50ZWdlcj4KCQkJPGludGVnZXI+MjwvaW50ZWdlcj4KCQkJ
47        PGludGVnZXI+MzwvaW50ZWdlcj4KCQk8L2FycmF5PgoJPC9hcnJheT4KCTxr
48        ZXk+YU5lZ2F0aXZlQmlnSW50PC9rZXk+Cgk8aW50ZWdlcj4tODAwMDAwMDAw
49        MDA8L2ludGVnZXI+Cgk8a2V5PmFOZWdhdGl2ZUludDwva2V5PgoJPGludGVn
50        ZXI+LTU8L2ludGVnZXI+Cgk8a2V5PmFTdHJpbmc8L2tleT4KCTxzdHJpbmc+
51        RG9vZGFoPC9zdHJpbmc+Cgk8a2V5PmFuRW1wdHlEaWN0PC9rZXk+Cgk8ZGlj
52        dC8+Cgk8a2V5PmFuRW1wdHlMaXN0PC9rZXk+Cgk8YXJyYXkvPgoJPGtleT5h
53        bkludDwva2V5PgoJPGludGVnZXI+NzI4PC9pbnRlZ2VyPgoJPGtleT5uZXN0
54        ZWREYXRhPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+CgkJUEd4dmRITWdiMlln
55        WW1sdVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5k
56        VzVyCgkJUGdBQkFnTThiRzkwY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJ
57        RFBHeHZkSE1nYjJZZ1ltbHVZWEo1CgkJSUdkMWJtcytBQUVDQXp4c2IzUnpJ
58        RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZaaUJpCgkJYVc1
59        aGNua2daM1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMr
60        QUFFQ0F6eHNiM1J6CgkJSUc5bUlHSnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJH
61        OTBjeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlECgkJUEd4dmRITWdiMlln
62        WW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09CgkJPC9kYXRhPgoJPC9hcnJheT4K
63        CTxrZXk+c29tZURhdGE8L2tleT4KCTxkYXRhPgoJUEdKcGJtRnllU0JuZFc1
64        clBnPT0KCTwvZGF0YT4KCTxrZXk+c29tZU1vcmVEYXRhPC9rZXk+Cgk8ZGF0
65        YT4KCVBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJ
66        RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004CgliRzkwY3lCdlppQmlhVzVo
67        Y25rZ1ozVnVhejRBQVFJRFBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytB
68        QUVDQXp4cwoJYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkw
69        Y3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHYKCWRITWdiMllnWW1s
70        dVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVy
71        UGdBQkFnTThiRzkwCgljeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlEUEd4
72        dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09Cgk8L2RhdGE+Cgk8
73        a2V5PsOFYmVucmFhPC9rZXk+Cgk8c3RyaW5nPlRoYXQgd2FzIGEgdW5pY29k
74        ZSBrZXkuPC9zdHJpbmc+CjwvZGljdD4KPC9wbGlzdD4K'''),
75    plistlib.FMT_BINARY: binascii.a2b_base64(b'''
76        YnBsaXN0MDDfEBABAgMEBQYHCAkKCwwNDg8QERITFCgpLzAxMjM0NTc2OFdh
77        QmlnSW50WGFCaWdJbnQyVWFEYXRlVWFEaWN0VmFGbG9hdFVhTGlzdF8QD2FO
78        ZWdhdGl2ZUJpZ0ludFxhTmVnYXRpdmVJbnRXYVN0cmluZ1thbkVtcHR5RGlj
79        dFthbkVtcHR5TGlzdFVhbkludFpuZXN0ZWREYXRhWHNvbWVEYXRhXHNvbWVN
80        b3JlRGF0YWcAxQBiAGUAbgByAGEAYRN/////////1BQAAAAAAAAAAIAAAAAA
81        AAAsM0GcuX30AAAA1RUWFxgZGhscHR5bYUZhbHNlVmFsdWVaYVRydWVWYWx1
82        ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGljdAgJawBN
83        AOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRoZXJlIT7T
84        HyAhIiMkUWFRYlFjEBEjQEBAAAAAAACjJSYnEAEQAlR0ZXh0Iz/gAAAAAAAA
85        pSorLCMtUUFRQhAMoyUmLhADE////+1foOAAE//////////7VkRvb2RhaNCg
86        EQLYoTZPEPo8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmlu
87        YXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBv
88        ZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs
89        b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4A
90        AQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBn
91        dW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vu
92        az5fEBdUaGF0IHdhcyBhIHVuaWNvZGUga2V5LgAIACsAMwA8AEIASABPAFUA
93        ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn
94        AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB
95        xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''),
96    'KEYED_ARCHIVE': binascii.a2b_base64(b'''
97        YnBsaXN0MDDUAQIDBAUGHB1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy
98        VCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVnB5dHlwZVYkY2xhc3NZTlMu
99        c3RyaW5nEAGAAl8QE0tleUFyY2hpdmUgVUlEIFRlc3TTEBESExQZWiRjbGFz
100        c25hbWVYJGNsYXNzZXNbJGNsYXNzaGludHNfEBdPQ19CdWlsdGluUHl0aG9u
101        VW5pY29kZaQVFhcYXxAXT0NfQnVpbHRpblB5dGhvblVuaWNvZGVfEBBPQ19Q
102        eXRob25Vbmljb2RlWE5TU3RyaW5nWE5TT2JqZWN0ohobXxAPT0NfUHl0aG9u
103        U3RyaW5nWE5TU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0R4fVHJvb3SAAQAI
104        ABEAGgAjAC0AMgA3ADsAQQBIAE8AVgBgAGIAZAB6AIEAjACVAKEAuwDAANoA
105        7QD2AP8BAgEUAR0BLwEyATcAAAAAAAACAQAAAAAAAAAgAAAAAAAAAAAAAAAA
106        AAABOQ=='''),
107}
108
109XML_PLIST_WITH_ENTITY=b'''\
110<?xml version="1.0" encoding="UTF-8"?>
111<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd" [
112   <!ENTITY entity "replacement text">
113  ]>
114<plist version="1.0">
115  <dict>
116    <key>A</key>
117    <string>&entity;</string>
118  </dict>
119</plist>
120'''
121
122INVALID_BINARY_PLISTS = [
123    ('too short data',
124        b''
125    ),
126    ('too large offset_table_offset and offset_size = 1',
127        b'\x00\x08'
128        b'\x00\x00\x00\x00\x00\x00\x01\x01'
129        b'\x00\x00\x00\x00\x00\x00\x00\x01'
130        b'\x00\x00\x00\x00\x00\x00\x00\x00'
131        b'\x00\x00\x00\x00\x00\x00\x00\x2a'
132    ),
133    ('too large offset_table_offset and nonstandard offset_size',
134        b'\x00\x00\x00\x08'
135        b'\x00\x00\x00\x00\x00\x00\x03\x01'
136        b'\x00\x00\x00\x00\x00\x00\x00\x01'
137        b'\x00\x00\x00\x00\x00\x00\x00\x00'
138        b'\x00\x00\x00\x00\x00\x00\x00\x2c'
139    ),
140    ('integer overflow in offset_table_offset',
141        b'\x00\x08'
142        b'\x00\x00\x00\x00\x00\x00\x01\x01'
143        b'\x00\x00\x00\x00\x00\x00\x00\x01'
144        b'\x00\x00\x00\x00\x00\x00\x00\x00'
145        b'\xff\xff\xff\xff\xff\xff\xff\xff'
146    ),
147    ('too large top_object',
148        b'\x00\x08'
149        b'\x00\x00\x00\x00\x00\x00\x01\x01'
150        b'\x00\x00\x00\x00\x00\x00\x00\x01'
151        b'\x00\x00\x00\x00\x00\x00\x00\x01'
152        b'\x00\x00\x00\x00\x00\x00\x00\x09'
153    ),
154    ('integer overflow in top_object',
155        b'\x00\x08'
156        b'\x00\x00\x00\x00\x00\x00\x01\x01'
157        b'\x00\x00\x00\x00\x00\x00\x00\x01'
158        b'\xff\xff\xff\xff\xff\xff\xff\xff'
159        b'\x00\x00\x00\x00\x00\x00\x00\x09'
160    ),
161    ('too large num_objects and offset_size = 1',
162        b'\x00\x08'
163        b'\x00\x00\x00\x00\x00\x00\x01\x01'
164        b'\x00\x00\x00\x00\x00\x00\x00\xff'
165        b'\x00\x00\x00\x00\x00\x00\x00\x00'
166        b'\x00\x00\x00\x00\x00\x00\x00\x09'
167    ),
168    ('too large num_objects and nonstandard offset_size',
169        b'\x00\x00\x00\x08'
170        b'\x00\x00\x00\x00\x00\x00\x03\x01'
171        b'\x00\x00\x00\x00\x00\x00\x00\xff'
172        b'\x00\x00\x00\x00\x00\x00\x00\x00'
173        b'\x00\x00\x00\x00\x00\x00\x00\x09'
174    ),
175    ('extremally large num_objects (32 bit)',
176        b'\x00\x08'
177        b'\x00\x00\x00\x00\x00\x00\x01\x01'
178        b'\x00\x00\x00\x00\x7f\xff\xff\xff'
179        b'\x00\x00\x00\x00\x00\x00\x00\x00'
180        b'\x00\x00\x00\x00\x00\x00\x00\x09'
181    ),
182    ('extremally large num_objects (64 bit)',
183        b'\x00\x08'
184        b'\x00\x00\x00\x00\x00\x00\x01\x01'
185        b'\x00\x00\x00\xff\xff\xff\xff\xff'
186        b'\x00\x00\x00\x00\x00\x00\x00\x00'
187        b'\x00\x00\x00\x00\x00\x00\x00\x09'
188    ),
189    ('integer overflow in num_objects',
190        b'\x00\x08'
191        b'\x00\x00\x00\x00\x00\x00\x01\x01'
192        b'\xff\xff\xff\xff\xff\xff\xff\xff'
193        b'\x00\x00\x00\x00\x00\x00\x00\x00'
194        b'\x00\x00\x00\x00\x00\x00\x00\x09'
195    ),
196    ('offset_size = 0',
197        b'\x00\x08'
198        b'\x00\x00\x00\x00\x00\x00\x00\x01'
199        b'\x00\x00\x00\x00\x00\x00\x00\x01'
200        b'\x00\x00\x00\x00\x00\x00\x00\x00'
201        b'\x00\x00\x00\x00\x00\x00\x00\x09'
202    ),
203    ('ref_size = 0',
204        b'\xa1\x01\x00\x08\x0a'
205        b'\x00\x00\x00\x00\x00\x00\x01\x00'
206        b'\x00\x00\x00\x00\x00\x00\x00\x02'
207        b'\x00\x00\x00\x00\x00\x00\x00\x00'
208        b'\x00\x00\x00\x00\x00\x00\x00\x0b'
209    ),
210    ('too large offset',
211        b'\x00\x2a'
212        b'\x00\x00\x00\x00\x00\x00\x01\x01'
213        b'\x00\x00\x00\x00\x00\x00\x00\x01'
214        b'\x00\x00\x00\x00\x00\x00\x00\x00'
215        b'\x00\x00\x00\x00\x00\x00\x00\x09'
216    ),
217    ('integer overflow in offset',
218        b'\x00\xff\xff\xff\xff\xff\xff\xff\xff'
219        b'\x00\x00\x00\x00\x00\x00\x08\x01'
220        b'\x00\x00\x00\x00\x00\x00\x00\x01'
221        b'\x00\x00\x00\x00\x00\x00\x00\x00'
222        b'\x00\x00\x00\x00\x00\x00\x00\x09'
223    ),
224    ('too large array size',
225        b'\xaf\x00\x01\xff\x00\x08\x0c'
226        b'\x00\x00\x00\x00\x00\x00\x01\x01'
227        b'\x00\x00\x00\x00\x00\x00\x00\x02'
228        b'\x00\x00\x00\x00\x00\x00\x00\x00'
229        b'\x00\x00\x00\x00\x00\x00\x00\x0d'
230    ),
231    ('extremally large array size (32-bit)',
232        b'\xaf\x02\x7f\xff\xff\xff\x01\x00\x08\x0f'
233        b'\x00\x00\x00\x00\x00\x00\x01\x01'
234        b'\x00\x00\x00\x00\x00\x00\x00\x02'
235        b'\x00\x00\x00\x00\x00\x00\x00\x00'
236        b'\x00\x00\x00\x00\x00\x00\x00\x10'
237    ),
238    ('extremally large array size (64-bit)',
239        b'\xaf\x03\x00\x00\x00\xff\xff\xff\xff\xff\x01\x00\x08\x13'
240        b'\x00\x00\x00\x00\x00\x00\x01\x01'
241        b'\x00\x00\x00\x00\x00\x00\x00\x02'
242        b'\x00\x00\x00\x00\x00\x00\x00\x00'
243        b'\x00\x00\x00\x00\x00\x00\x00\x14'
244    ),
245    ('integer overflow in array size',
246        b'\xaf\x03\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x08\x13'
247        b'\x00\x00\x00\x00\x00\x00\x01\x01'
248        b'\x00\x00\x00\x00\x00\x00\x00\x02'
249        b'\x00\x00\x00\x00\x00\x00\x00\x00'
250        b'\x00\x00\x00\x00\x00\x00\x00\x14'
251    ),
252    ('too large reference index',
253        b'\xa1\x02\x00\x08\x0a'
254        b'\x00\x00\x00\x00\x00\x00\x01\x01'
255        b'\x00\x00\x00\x00\x00\x00\x00\x02'
256        b'\x00\x00\x00\x00\x00\x00\x00\x00'
257        b'\x00\x00\x00\x00\x00\x00\x00\x0b'
258    ),
259    ('integer overflow in reference index',
260        b'\xa1\xff\xff\xff\xff\xff\xff\xff\xff\x00\x08\x11'
261        b'\x00\x00\x00\x00\x00\x00\x01\x08'
262        b'\x00\x00\x00\x00\x00\x00\x00\x02'
263        b'\x00\x00\x00\x00\x00\x00\x00\x00'
264        b'\x00\x00\x00\x00\x00\x00\x00\x12'
265    ),
266    ('too large bytes size',
267        b'\x4f\x00\x23\x41\x08'
268        b'\x00\x00\x00\x00\x00\x00\x01\x01'
269        b'\x00\x00\x00\x00\x00\x00\x00\x01'
270        b'\x00\x00\x00\x00\x00\x00\x00\x00'
271        b'\x00\x00\x00\x00\x00\x00\x00\x0c'
272    ),
273    ('extremally large bytes size (32-bit)',
274        b'\x4f\x02\x7f\xff\xff\xff\x41\x08'
275        b'\x00\x00\x00\x00\x00\x00\x01\x01'
276        b'\x00\x00\x00\x00\x00\x00\x00\x01'
277        b'\x00\x00\x00\x00\x00\x00\x00\x00'
278        b'\x00\x00\x00\x00\x00\x00\x00\x0f'
279    ),
280    ('extremally large bytes size (64-bit)',
281        b'\x4f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08'
282        b'\x00\x00\x00\x00\x00\x00\x01\x01'
283        b'\x00\x00\x00\x00\x00\x00\x00\x01'
284        b'\x00\x00\x00\x00\x00\x00\x00\x00'
285        b'\x00\x00\x00\x00\x00\x00\x00\x13'
286    ),
287    ('integer overflow in bytes size',
288        b'\x4f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08'
289        b'\x00\x00\x00\x00\x00\x00\x01\x01'
290        b'\x00\x00\x00\x00\x00\x00\x00\x01'
291        b'\x00\x00\x00\x00\x00\x00\x00\x00'
292        b'\x00\x00\x00\x00\x00\x00\x00\x13'
293    ),
294    ('too large ASCII size',
295        b'\x5f\x00\x23\x41\x08'
296        b'\x00\x00\x00\x00\x00\x00\x01\x01'
297        b'\x00\x00\x00\x00\x00\x00\x00\x01'
298        b'\x00\x00\x00\x00\x00\x00\x00\x00'
299        b'\x00\x00\x00\x00\x00\x00\x00\x0c'
300    ),
301    ('extremally large ASCII size (32-bit)',
302        b'\x5f\x02\x7f\xff\xff\xff\x41\x08'
303        b'\x00\x00\x00\x00\x00\x00\x01\x01'
304        b'\x00\x00\x00\x00\x00\x00\x00\x01'
305        b'\x00\x00\x00\x00\x00\x00\x00\x00'
306        b'\x00\x00\x00\x00\x00\x00\x00\x0f'
307    ),
308    ('extremally large ASCII size (64-bit)',
309        b'\x5f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08'
310        b'\x00\x00\x00\x00\x00\x00\x01\x01'
311        b'\x00\x00\x00\x00\x00\x00\x00\x01'
312        b'\x00\x00\x00\x00\x00\x00\x00\x00'
313        b'\x00\x00\x00\x00\x00\x00\x00\x13'
314    ),
315    ('integer overflow in ASCII size',
316        b'\x5f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08'
317        b'\x00\x00\x00\x00\x00\x00\x01\x01'
318        b'\x00\x00\x00\x00\x00\x00\x00\x01'
319        b'\x00\x00\x00\x00\x00\x00\x00\x00'
320        b'\x00\x00\x00\x00\x00\x00\x00\x13'
321    ),
322    ('invalid ASCII',
323        b'\x51\xff\x08'
324        b'\x00\x00\x00\x00\x00\x00\x01\x01'
325        b'\x00\x00\x00\x00\x00\x00\x00\x01'
326        b'\x00\x00\x00\x00\x00\x00\x00\x00'
327        b'\x00\x00\x00\x00\x00\x00\x00\x0a'
328    ),
329    ('too large UTF-16 size',
330        b'\x6f\x00\x13\x20\xac\x00\x08'
331        b'\x00\x00\x00\x00\x00\x00\x01\x01'
332        b'\x00\x00\x00\x00\x00\x00\x00\x01'
333        b'\x00\x00\x00\x00\x00\x00\x00\x00'
334        b'\x00\x00\x00\x00\x00\x00\x00\x0e'
335    ),
336    ('extremally large UTF-16 size (32-bit)',
337        b'\x6f\x02\x4f\xff\xff\xff\x20\xac\x00\x08'
338        b'\x00\x00\x00\x00\x00\x00\x01\x01'
339        b'\x00\x00\x00\x00\x00\x00\x00\x01'
340        b'\x00\x00\x00\x00\x00\x00\x00\x00'
341        b'\x00\x00\x00\x00\x00\x00\x00\x11'
342    ),
343    ('extremally large UTF-16 size (64-bit)',
344        b'\x6f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x20\xac\x00\x08'
345        b'\x00\x00\x00\x00\x00\x00\x01\x01'
346        b'\x00\x00\x00\x00\x00\x00\x00\x01'
347        b'\x00\x00\x00\x00\x00\x00\x00\x00'
348        b'\x00\x00\x00\x00\x00\x00\x00\x15'
349    ),
350    ('integer overflow in UTF-16 size',
351        b'\x6f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x20\xac\x00\x08'
352        b'\x00\x00\x00\x00\x00\x00\x01\x01'
353        b'\x00\x00\x00\x00\x00\x00\x00\x01'
354        b'\x00\x00\x00\x00\x00\x00\x00\x00'
355        b'\x00\x00\x00\x00\x00\x00\x00\x15'
356    ),
357    ('invalid UTF-16',
358        b'\x61\xd8\x00\x08'
359        b'\x00\x00\x00\x00\x00\x00\x01\x01'
360        b'\x00\x00\x00\x00\x00\x00\x00\x01'
361        b'\x00\x00\x00\x00\x00\x00\x00\x00'
362        b'\x00\x00\x00\x00\x00\x00\x00\x0b'
363    ),
364    ('non-hashable key',
365        b'\xd1\x01\x01\xa0\x08\x0b'
366        b'\x00\x00\x00\x00\x00\x00\x01\x01'
367        b'\x00\x00\x00\x00\x00\x00\x00\x02'
368        b'\x00\x00\x00\x00\x00\x00\x00\x00'
369        b'\x00\x00\x00\x00\x00\x00\x00\x0c'
370    ),
371    ('too large datetime (datetime overflow)',
372        b'\x33\x42\x50\x00\x00\x00\x00\x00\x00\x08'
373        b'\x00\x00\x00\x00\x00\x00\x01\x01'
374        b'\x00\x00\x00\x00\x00\x00\x00\x01'
375        b'\x00\x00\x00\x00\x00\x00\x00\x00'
376        b'\x00\x00\x00\x00\x00\x00\x00\x11'
377    ),
378    ('too large datetime (timedelta overflow)',
379        b'\x33\x42\xe0\x00\x00\x00\x00\x00\x00\x08'
380        b'\x00\x00\x00\x00\x00\x00\x01\x01'
381        b'\x00\x00\x00\x00\x00\x00\x00\x01'
382        b'\x00\x00\x00\x00\x00\x00\x00\x00'
383        b'\x00\x00\x00\x00\x00\x00\x00\x11'
384    ),
385    ('invalid datetime (Infinity)',
386        b'\x33\x7f\xf0\x00\x00\x00\x00\x00\x00\x08'
387        b'\x00\x00\x00\x00\x00\x00\x01\x01'
388        b'\x00\x00\x00\x00\x00\x00\x00\x01'
389        b'\x00\x00\x00\x00\x00\x00\x00\x00'
390        b'\x00\x00\x00\x00\x00\x00\x00\x11'
391    ),
392    ('invalid datetime (NaN)',
393        b'\x33\x7f\xf8\x00\x00\x00\x00\x00\x00\x08'
394        b'\x00\x00\x00\x00\x00\x00\x01\x01'
395        b'\x00\x00\x00\x00\x00\x00\x00\x01'
396        b'\x00\x00\x00\x00\x00\x00\x00\x00'
397        b'\x00\x00\x00\x00\x00\x00\x00\x11'
398    ),
399]
400
401
402class TestPlistlib(unittest.TestCase):
403
404    def tearDown(self):
405        try:
406            os.unlink(support.TESTFN)
407        except:
408            pass
409
410    def _create(self, fmt=None):
411        pl = dict(
412            aString="Doodah",
413            aList=["A", "B", 12, 32.5, [1, 2, 3]],
414            aFloat = 0.5,
415            anInt = 728,
416            aBigInt = 2 ** 63 - 44,
417            aBigInt2 = 2 ** 63 + 44,
418            aNegativeInt = -5,
419            aNegativeBigInt = -80000000000,
420            aDict=dict(
421                anotherString="<hello & 'hi' there!>",
422                aUnicodeValue='M\xe4ssig, Ma\xdf',
423                aTrueValue=True,
424                aFalseValue=False,
425                deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]),
426            ),
427            someData = b"<binary gunk>",
428            someMoreData = b"<lots of binary gunk>\0\1\2\3" * 10,
429            nestedData = [b"<lots of binary gunk>\0\1\2\3" * 10],
430            aDate = datetime.datetime(2004, 10, 26, 10, 33, 33),
431            anEmptyDict = dict(),
432            anEmptyList = list()
433        )
434        pl['\xc5benraa'] = "That was a unicode key."
435        return pl
436
437    def test_create(self):
438        pl = self._create()
439        self.assertEqual(pl["aString"], "Doodah")
440        self.assertEqual(pl["aDict"]["aFalseValue"], False)
441
442    def test_io(self):
443        pl = self._create()
444        with open(support.TESTFN, 'wb') as fp:
445            plistlib.dump(pl, fp)
446
447        with open(support.TESTFN, 'rb') as fp:
448            pl2 = plistlib.load(fp)
449
450        self.assertEqual(dict(pl), dict(pl2))
451
452        self.assertRaises(AttributeError, plistlib.dump, pl, 'filename')
453        self.assertRaises(AttributeError, plistlib.load, 'filename')
454
455    def test_invalid_type(self):
456        pl = [ object() ]
457
458        for fmt in ALL_FORMATS:
459            with self.subTest(fmt=fmt):
460                self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
461
462    def test_invalid_uid(self):
463        with self.assertRaises(TypeError):
464            UID("not an int")
465        with self.assertRaises(ValueError):
466            UID(2 ** 64)
467        with self.assertRaises(ValueError):
468            UID(-19)
469
470    def test_int(self):
471        for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32,
472                   2**63-1, 2**64-1, 1, -2**63]:
473            for fmt in ALL_FORMATS:
474                with self.subTest(pl=pl, fmt=fmt):
475                    data = plistlib.dumps(pl, fmt=fmt)
476                    pl2 = plistlib.loads(data)
477                    self.assertIsInstance(pl2, int)
478                    self.assertEqual(pl, pl2)
479                    data2 = plistlib.dumps(pl2, fmt=fmt)
480                    self.assertEqual(data, data2)
481
482        for fmt in ALL_FORMATS:
483            for pl in (2 ** 64 + 1, 2 ** 127-1, -2**64, -2 ** 127):
484                with self.subTest(pl=pl, fmt=fmt):
485                    self.assertRaises(OverflowError, plistlib.dumps,
486                                      pl, fmt=fmt)
487
488    def test_bytearray(self):
489        for pl in (b'<binary gunk>', b"<lots of binary gunk>\0\1\2\3" * 10):
490            for fmt in ALL_FORMATS:
491                with self.subTest(pl=pl, fmt=fmt):
492                    data = plistlib.dumps(bytearray(pl), fmt=fmt)
493                    pl2 = plistlib.loads(data)
494                    self.assertIsInstance(pl2, bytes)
495                    self.assertEqual(pl2, pl)
496                    data2 = plistlib.dumps(pl2, fmt=fmt)
497                    self.assertEqual(data, data2)
498
499    def test_bytes(self):
500        pl = self._create()
501        data = plistlib.dumps(pl)
502        pl2 = plistlib.loads(data)
503        self.assertEqual(dict(pl), dict(pl2))
504        data2 = plistlib.dumps(pl2)
505        self.assertEqual(data, data2)
506
507    def test_indentation_array(self):
508        data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]]
509        self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
510
511    def test_indentation_dict(self):
512        data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}}
513        self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
514
515    def test_indentation_dict_mix(self):
516        data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}}
517        self.assertEqual(plistlib.loads(plistlib.dumps(data)), data)
518
519    def test_uid(self):
520        data = UID(1)
521        self.assertEqual(plistlib.loads(plistlib.dumps(data, fmt=plistlib.FMT_BINARY)), data)
522        dict_data = {
523            'uid0': UID(0),
524            'uid2': UID(2),
525            'uid8': UID(2 ** 8),
526            'uid16': UID(2 ** 16),
527            'uid32': UID(2 ** 32),
528            'uid63': UID(2 ** 63)
529        }
530        self.assertEqual(plistlib.loads(plistlib.dumps(dict_data, fmt=plistlib.FMT_BINARY)), dict_data)
531
532    def test_uid_data(self):
533        uid = UID(1)
534        self.assertEqual(uid.data, 1)
535
536    def test_uid_eq(self):
537        self.assertEqual(UID(1), UID(1))
538        self.assertNotEqual(UID(1), UID(2))
539        self.assertNotEqual(UID(1), "not uid")
540
541    def test_uid_hash(self):
542        self.assertEqual(hash(UID(1)), hash(UID(1)))
543
544    def test_uid_repr(self):
545        self.assertEqual(repr(UID(1)), "UID(1)")
546
547    def test_uid_index(self):
548        self.assertEqual(operator.index(UID(1)), 1)
549
550    def test_uid_pickle(self):
551        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
552            self.assertEqual(pickle.loads(pickle.dumps(UID(19), protocol=proto)), UID(19))
553
554    def test_uid_copy(self):
555        self.assertEqual(copy.copy(UID(1)), UID(1))
556        self.assertEqual(copy.deepcopy(UID(1)), UID(1))
557
558    def test_appleformatting(self):
559        for fmt in ALL_FORMATS:
560            with self.subTest(fmt=fmt):
561                pl = plistlib.loads(TESTDATA[fmt])
562                data = plistlib.dumps(pl, fmt=fmt)
563                self.assertEqual(data, TESTDATA[fmt],
564                    "generated data was not identical to Apple's output")
565
566
567    def test_appleformattingfromliteral(self):
568        self.maxDiff = None
569        for fmt in ALL_FORMATS:
570            with self.subTest(fmt=fmt):
571                pl = self._create(fmt=fmt)
572                pl2 = plistlib.loads(TESTDATA[fmt], fmt=fmt)
573                self.assertEqual(dict(pl), dict(pl2),
574                    "generated data was not identical to Apple's output")
575                pl2 = plistlib.loads(TESTDATA[fmt])
576                self.assertEqual(dict(pl), dict(pl2),
577                    "generated data was not identical to Apple's output")
578
579    def test_bytesio(self):
580        for fmt in ALL_FORMATS:
581            with self.subTest(fmt=fmt):
582                b = BytesIO()
583                pl = self._create(fmt=fmt)
584                plistlib.dump(pl, b, fmt=fmt)
585                pl2 = plistlib.load(BytesIO(b.getvalue()), fmt=fmt)
586                self.assertEqual(dict(pl), dict(pl2))
587                pl2 = plistlib.load(BytesIO(b.getvalue()))
588                self.assertEqual(dict(pl), dict(pl2))
589
590    def test_keysort_bytesio(self):
591        pl = collections.OrderedDict()
592        pl['b'] = 1
593        pl['a'] = 2
594        pl['c'] = 3
595
596        for fmt in ALL_FORMATS:
597            for sort_keys in (False, True):
598                with self.subTest(fmt=fmt, sort_keys=sort_keys):
599                    b = BytesIO()
600
601                    plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys)
602                    pl2 = plistlib.load(BytesIO(b.getvalue()),
603                        dict_type=collections.OrderedDict)
604
605                    self.assertEqual(dict(pl), dict(pl2))
606                    if sort_keys:
607                        self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
608                    else:
609                        self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
610
611    def test_keysort(self):
612        pl = collections.OrderedDict()
613        pl['b'] = 1
614        pl['a'] = 2
615        pl['c'] = 3
616
617        for fmt in ALL_FORMATS:
618            for sort_keys in (False, True):
619                with self.subTest(fmt=fmt, sort_keys=sort_keys):
620                    data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys)
621                    pl2 = plistlib.loads(data, dict_type=collections.OrderedDict)
622
623                    self.assertEqual(dict(pl), dict(pl2))
624                    if sort_keys:
625                        self.assertEqual(list(pl2.keys()), ['a', 'b', 'c'])
626                    else:
627                        self.assertEqual(list(pl2.keys()), ['b', 'a', 'c'])
628
629    def test_keys_no_string(self):
630        pl = { 42: 'aNumber' }
631
632        for fmt in ALL_FORMATS:
633            with self.subTest(fmt=fmt):
634                self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt)
635
636                b = BytesIO()
637                self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt)
638
639    def test_skipkeys(self):
640        pl = {
641            42: 'aNumber',
642            'snake': 'aWord',
643        }
644
645        for fmt in ALL_FORMATS:
646            with self.subTest(fmt=fmt):
647                data = plistlib.dumps(
648                    pl, fmt=fmt, skipkeys=True, sort_keys=False)
649
650                pl2 = plistlib.loads(data)
651                self.assertEqual(pl2, {'snake': 'aWord'})
652
653                fp = BytesIO()
654                plistlib.dump(
655                    pl, fp, fmt=fmt, skipkeys=True, sort_keys=False)
656                data = fp.getvalue()
657                pl2 = plistlib.loads(fp.getvalue())
658                self.assertEqual(pl2, {'snake': 'aWord'})
659
660    def test_tuple_members(self):
661        pl = {
662            'first': (1, 2),
663            'second': (1, 2),
664            'third': (3, 4),
665        }
666
667        for fmt in ALL_FORMATS:
668            with self.subTest(fmt=fmt):
669                data = plistlib.dumps(pl, fmt=fmt)
670                pl2 = plistlib.loads(data)
671                self.assertEqual(pl2, {
672                    'first': [1, 2],
673                    'second': [1, 2],
674                    'third': [3, 4],
675                })
676                if fmt != plistlib.FMT_BINARY:
677                    self.assertIsNot(pl2['first'], pl2['second'])
678
679    def test_list_members(self):
680        pl = {
681            'first': [1, 2],
682            'second': [1, 2],
683            'third': [3, 4],
684        }
685
686        for fmt in ALL_FORMATS:
687            with self.subTest(fmt=fmt):
688                data = plistlib.dumps(pl, fmt=fmt)
689                pl2 = plistlib.loads(data)
690                self.assertEqual(pl2, {
691                    'first': [1, 2],
692                    'second': [1, 2],
693                    'third': [3, 4],
694                })
695                self.assertIsNot(pl2['first'], pl2['second'])
696
697    def test_dict_members(self):
698        pl = {
699            'first': {'a': 1},
700            'second': {'a': 1},
701            'third': {'b': 2 },
702        }
703
704        for fmt in ALL_FORMATS:
705            with self.subTest(fmt=fmt):
706                data = plistlib.dumps(pl, fmt=fmt)
707                pl2 = plistlib.loads(data)
708                self.assertEqual(pl2, {
709                    'first': {'a': 1},
710                    'second': {'a': 1},
711                    'third': {'b': 2 },
712                })
713                self.assertIsNot(pl2['first'], pl2['second'])
714
715    def test_controlcharacters(self):
716        for i in range(128):
717            c = chr(i)
718            testString = "string containing %s" % c
719            if i >= 32 or c in "\r\n\t":
720                # \r, \n and \t are the only legal control chars in XML
721                data = plistlib.dumps(testString, fmt=plistlib.FMT_XML)
722                if c != "\r":
723                    self.assertEqual(plistlib.loads(data), testString)
724            else:
725                with self.assertRaises(ValueError):
726                    plistlib.dumps(testString, fmt=plistlib.FMT_XML)
727            plistlib.dumps(testString, fmt=plistlib.FMT_BINARY)
728
729    def test_non_bmp_characters(self):
730        pl = {'python': '\U0001f40d'}
731        for fmt in ALL_FORMATS:
732            with self.subTest(fmt=fmt):
733                data = plistlib.dumps(pl, fmt=fmt)
734                self.assertEqual(plistlib.loads(data), pl)
735
736    def test_lone_surrogates(self):
737        for fmt in ALL_FORMATS:
738            with self.subTest(fmt=fmt):
739                with self.assertRaises(UnicodeEncodeError):
740                    plistlib.dumps('\ud8ff', fmt=fmt)
741                with self.assertRaises(UnicodeEncodeError):
742                    plistlib.dumps('\udcff', fmt=fmt)
743
744    def test_nondictroot(self):
745        for fmt in ALL_FORMATS:
746            with self.subTest(fmt=fmt):
747                test1 = "abc"
748                test2 = [1, 2, 3, "abc"]
749                result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt))
750                result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt))
751                self.assertEqual(test1, result1)
752                self.assertEqual(test2, result2)
753
754    def test_invalidarray(self):
755        for i in ["<key>key inside an array</key>",
756                  "<key>key inside an array2</key><real>3</real>",
757                  "<true/><key>key inside an array3</key>"]:
758            self.assertRaises(ValueError, plistlib.loads,
759                              ("<plist><array>%s</array></plist>"%i).encode())
760
761    def test_invaliddict(self):
762        for i in ["<key><true/>k</key><string>compound key</string>",
763                  "<key>single key</key>",
764                  "<string>missing key</string>",
765                  "<key>k1</key><string>v1</string><real>5.3</real>"
766                  "<key>k1</key><key>k2</key><string>double key</string>"]:
767            self.assertRaises(ValueError, plistlib.loads,
768                              ("<plist><dict>%s</dict></plist>"%i).encode())
769            self.assertRaises(ValueError, plistlib.loads,
770                              ("<plist><array><dict>%s</dict></array></plist>"%i).encode())
771
772    def test_invalidinteger(self):
773        self.assertRaises(ValueError, plistlib.loads,
774                          b"<plist><integer>not integer</integer></plist>")
775
776    def test_invalidreal(self):
777        self.assertRaises(ValueError, plistlib.loads,
778                          b"<plist><integer>not real</integer></plist>")
779
780    def test_integer_notations(self):
781        pl = b"<plist><integer>456</integer></plist>"
782        value = plistlib.loads(pl)
783        self.assertEqual(value, 456)
784
785        pl = b"<plist><integer>0xa</integer></plist>"
786        value = plistlib.loads(pl)
787        self.assertEqual(value, 10)
788
789        pl = b"<plist><integer>0123</integer></plist>"
790        value = plistlib.loads(pl)
791        self.assertEqual(value, 123)
792
793    def test_xml_encodings(self):
794        base = TESTDATA[plistlib.FMT_XML]
795
796        for xml_encoding, encoding, bom in [
797                    (b'utf-8', 'utf-8', codecs.BOM_UTF8),
798                    (b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE),
799                    (b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE),
800                    # Expat does not support UTF-32
801                    #(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE),
802                    #(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE),
803                ]:
804
805            pl = self._create(fmt=plistlib.FMT_XML)
806            with self.subTest(encoding=encoding):
807                data = base.replace(b'UTF-8', xml_encoding)
808                data = bom + data.decode('utf-8').encode(encoding)
809                pl2 = plistlib.loads(data)
810                self.assertEqual(dict(pl), dict(pl2))
811
812    def test_dump_invalid_format(self):
813        with self.assertRaises(ValueError):
814            plistlib.dumps({}, fmt="blah")
815
816    def test_load_invalid_file(self):
817        with self.assertRaises(plistlib.InvalidFileException):
818            plistlib.loads(b"these are not plist file contents")
819
820    def test_modified_uid_negative(self):
821        neg_uid = UID(1)
822        neg_uid.data = -1  # dodge the negative check in the constructor
823        with self.assertRaises(ValueError):
824            plistlib.dumps(neg_uid, fmt=plistlib.FMT_BINARY)
825
826    def test_modified_uid_huge(self):
827        huge_uid = UID(1)
828        huge_uid.data = 2 ** 64  # dodge the size check in the constructor
829        with self.assertRaises(OverflowError):
830            plistlib.dumps(huge_uid, fmt=plistlib.FMT_BINARY)
831
832    def test_xml_plist_with_entity_decl(self):
833        with self.assertRaisesRegex(plistlib.InvalidFileException,
834                                    "XML entity declarations are not supported"):
835            plistlib.loads(XML_PLIST_WITH_ENTITY, fmt=plistlib.FMT_XML)
836
837
838class TestBinaryPlistlib(unittest.TestCase):
839
840    @staticmethod
841    def decode(*objects, offset_size=1, ref_size=1):
842        data = [b'bplist00']
843        offset = 8
844        offsets = []
845        for x in objects:
846            offsets.append(offset.to_bytes(offset_size, 'big'))
847            data.append(x)
848            offset += len(x)
849        tail = struct.pack('>6xBBQQQ', offset_size, ref_size,
850                           len(objects), 0, offset)
851        data.extend(offsets)
852        data.append(tail)
853        return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY)
854
855    def test_nonstandard_refs_size(self):
856        # Issue #21538: Refs and offsets are 24-bit integers
857        data = (b'bplist00'
858                b'\xd1\x00\x00\x01\x00\x00\x02QaQb'
859                b'\x00\x00\x08\x00\x00\x0f\x00\x00\x11'
860                b'\x00\x00\x00\x00\x00\x00'
861                b'\x03\x03'
862                b'\x00\x00\x00\x00\x00\x00\x00\x03'
863                b'\x00\x00\x00\x00\x00\x00\x00\x00'
864                b'\x00\x00\x00\x00\x00\x00\x00\x13')
865        self.assertEqual(plistlib.loads(data), {'a': 'b'})
866
867    def test_dump_duplicates(self):
868        # Test effectiveness of saving duplicated objects
869        for x in (None, False, True, 12345, 123.45, 'abcde', 'абвгд', b'abcde',
870                  datetime.datetime(2004, 10, 26, 10, 33, 33),
871                  bytearray(b'abcde'), [12, 345], (12, 345), {'12': 345}):
872            with self.subTest(x=x):
873                data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY)
874                self.assertLess(len(data), 1100, repr(data))
875
876    def test_identity(self):
877        for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde',
878                  datetime.datetime(2004, 10, 26, 10, 33, 33),
879                  bytearray(b'abcde'), [12, 345], (12, 345), {'12': 345}):
880            with self.subTest(x=x):
881                data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY)
882                a, b = plistlib.loads(data)
883                if isinstance(x, tuple):
884                    x = list(x)
885                self.assertEqual(a, x)
886                self.assertEqual(b, x)
887                self.assertIs(a, b)
888
889    def test_cycles(self):
890        # recursive list
891        a = []
892        a.append(a)
893        b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
894        self.assertIs(b[0], b)
895        # recursive tuple
896        a = ([],)
897        a[0].append(a)
898        b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
899        self.assertIs(b[0][0], b)
900        # recursive dict
901        a = {}
902        a['x'] = a
903        b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY))
904        self.assertIs(b['x'], b)
905
906    def test_deep_nesting(self):
907        for N in [300, 100000]:
908            chunks = [b'\xa1' + (i + 1).to_bytes(4, 'big') for i in range(N)]
909            try:
910                result = self.decode(*chunks, b'\x54seed', offset_size=4, ref_size=4)
911            except RecursionError:
912                pass
913            else:
914                for i in range(N):
915                    self.assertIsInstance(result, list)
916                    self.assertEqual(len(result), 1)
917                    result = result[0]
918                self.assertEqual(result, 'seed')
919
920    def test_large_timestamp(self):
921        # Issue #26709: 32-bit timestamp out of range
922        for ts in -2**31-1, 2**31:
923            with self.subTest(ts=ts):
924                d = (datetime.datetime.utcfromtimestamp(0) +
925                     datetime.timedelta(seconds=ts))
926                data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY)
927                self.assertEqual(plistlib.loads(data), d)
928
929    def test_load_singletons(self):
930        self.assertIs(self.decode(b'\x00'), None)
931        self.assertIs(self.decode(b'\x08'), False)
932        self.assertIs(self.decode(b'\x09'), True)
933        self.assertEqual(self.decode(b'\x0f'), b'')
934
935    def test_load_int(self):
936        self.assertEqual(self.decode(b'\x10\x00'), 0)
937        self.assertEqual(self.decode(b'\x10\xfe'), 0xfe)
938        self.assertEqual(self.decode(b'\x11\xfe\xdc'), 0xfedc)
939        self.assertEqual(self.decode(b'\x12\xfe\xdc\xba\x98'), 0xfedcba98)
940        self.assertEqual(self.decode(b'\x13\x01\x23\x45\x67\x89\xab\xcd\xef'),
941                         0x0123456789abcdef)
942        self.assertEqual(self.decode(b'\x13\xfe\xdc\xba\x98\x76\x54\x32\x10'),
943                         -0x123456789abcdf0)
944
945    def test_unsupported(self):
946        unsupported = [*range(1, 8), *range(10, 15),
947                       0x20, 0x21, *range(0x24, 0x33), *range(0x34, 0x40)]
948        for i in [0x70, 0x90, 0xb0, 0xc0, 0xe0, 0xf0]:
949            unsupported.extend(i + j for j in range(16))
950        for token in unsupported:
951            with self.subTest(f'token {token:02x}'):
952                with self.assertRaises(plistlib.InvalidFileException):
953                    self.decode(bytes([token]) + b'\x00'*16)
954
955    def test_invalid_binary(self):
956        for name, data in INVALID_BINARY_PLISTS:
957            with self.subTest(name):
958                with self.assertRaises(plistlib.InvalidFileException):
959                    plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY)
960
961
962class TestKeyedArchive(unittest.TestCase):
963    def test_keyed_archive_data(self):
964        # This is the structure of a NSKeyedArchive packed plist
965        data = {
966            '$version': 100000,
967            '$objects': [
968                '$null', {
969                    'pytype': 1,
970                    '$class': UID(2),
971                    'NS.string': 'KeyArchive UID Test'
972                },
973                {
974                    '$classname': 'OC_BuiltinPythonUnicode',
975                    '$classes': [
976                        'OC_BuiltinPythonUnicode',
977                        'OC_PythonUnicode',
978                        'NSString',
979                        'NSObject'
980                    ],
981                    '$classhints': [
982                        'OC_PythonString', 'NSString'
983                    ]
984                }
985            ],
986            '$archiver': 'NSKeyedArchiver',
987            '$top': {
988                'root': UID(1)
989            }
990        }
991        self.assertEqual(plistlib.loads(TESTDATA["KEYED_ARCHIVE"]), data)
992
993
994class MiscTestCase(unittest.TestCase):
995    def test__all__(self):
996        blacklist = {"PlistFormat", "PLISTHEADER"}
997        support.check__all__(self, plistlib, blacklist=blacklist)
998
999
1000if __name__ == '__main__':
1001    unittest.main()
1002