1# Copyright (C) 2003-2013 Python Software Foundation 2import copy 3import operator 4import pickle 5import struct 6import unittest 7import plistlib 8import os 9import sys 10import json 11import datetime 12import codecs 13import subprocess 14import binascii 15import collections 16from test import support 17from test.support import os_helper 18from io import BytesIO 19 20from plistlib import UID 21 22ALL_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) 26TESTDATA={ 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 113XML_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 126INVALID_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 406class TestPlistlib(unittest.TestCase): 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 842class TestBinaryPlistlib(unittest.TestCase): 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.utcfromtimestamp(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 966class TestKeyedArchive(unittest.TestCase): 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 998class MiscTestCase(unittest.TestCase): 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") 1004class TestPlutil(unittest.TestCase): 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 1075if __name__ == '__main__': 1076 unittest.main() 1077