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