diff --git a/Lib/test/test_plistlib.py b/Lib/test/test_plistlib.py new file mode 100644 index 000000000..e4c4e0d3b --- /dev/null +++ b/Lib/test/test_plistlib.py @@ -0,0 +1,1016 @@ +# Copyright (C) 2003-2013 Python Software Foundation +import copy +import operator +import pickle +import struct +import unittest +import plistlib +import os +import datetime +import codecs +import binascii +import collections +from test import support +from test.support import os_helper +from io import BytesIO + +from plistlib import UID + +ALL_FORMATS=(plistlib.FMT_XML, plistlib.FMT_BINARY) + +# The testdata is generated using Mac/Tools/plistlib_generate_testdata.py +# (which using PyObjC to control the Cocoa classes for generating plists) +TESTDATA={ + plistlib.FMT_XML: binascii.a2b_base64(b''' + PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU + WVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO + IiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w + LmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+YUJp + Z0ludDwva2V5PgoJPGludGVnZXI+OTIyMzM3MjAzNjg1NDc3NTc2NDwvaW50 + ZWdlcj4KCTxrZXk+YUJpZ0ludDI8L2tleT4KCTxpbnRlZ2VyPjkyMjMzNzIw + MzY4NTQ3NzU4NTI8L2ludGVnZXI+Cgk8a2V5PmFEYXRlPC9rZXk+Cgk8ZGF0 + ZT4yMDA0LTEwLTI2VDEwOjMzOjMzWjwvZGF0ZT4KCTxrZXk+YURpY3Q8L2tl + eT4KCTxkaWN0PgoJCTxrZXk+YUZhbHNlVmFsdWU8L2tleT4KCQk8ZmFsc2Uv + PgoJCTxrZXk+YVRydWVWYWx1ZTwva2V5PgoJCTx0cnVlLz4KCQk8a2V5PmFV + bmljb2RlVmFsdWU8L2tleT4KCQk8c3RyaW5nPk3DpHNzaWcsIE1hw588L3N0 + cmluZz4KCQk8a2V5PmFub3RoZXJTdHJpbmc8L2tleT4KCQk8c3RyaW5nPiZs + dDtoZWxsbyAmYW1wOyAnaGknIHRoZXJlISZndDs8L3N0cmluZz4KCQk8a2V5 + PmRlZXBlckRpY3Q8L2tleT4KCQk8ZGljdD4KCQkJPGtleT5hPC9rZXk+CgkJ + CTxpbnRlZ2VyPjE3PC9pbnRlZ2VyPgoJCQk8a2V5PmI8L2tleT4KCQkJPHJl + YWw+MzIuNTwvcmVhbD4KCQkJPGtleT5jPC9rZXk+CgkJCTxhcnJheT4KCQkJ + CTxpbnRlZ2VyPjE8L2ludGVnZXI+CgkJCQk8aW50ZWdlcj4yPC9pbnRlZ2Vy + PgoJCQkJPHN0cmluZz50ZXh0PC9zdHJpbmc+CgkJCTwvYXJyYXk+CgkJPC9k + aWN0PgoJPC9kaWN0PgoJPGtleT5hRmxvYXQ8L2tleT4KCTxyZWFsPjAuNTwv + cmVhbD4KCTxrZXk+YUxpc3Q8L2tleT4KCTxhcnJheT4KCQk8c3RyaW5nPkE8 + L3N0cmluZz4KCQk8c3RyaW5nPkI8L3N0cmluZz4KCQk8aW50ZWdlcj4xMjwv + aW50ZWdlcj4KCQk8cmVhbD4zMi41PC9yZWFsPgoJCTxhcnJheT4KCQkJPGlu + dGVnZXI+MTwvaW50ZWdlcj4KCQkJPGludGVnZXI+MjwvaW50ZWdlcj4KCQkJ + PGludGVnZXI+MzwvaW50ZWdlcj4KCQk8L2FycmF5PgoJPC9hcnJheT4KCTxr + ZXk+YU5lZ2F0aXZlQmlnSW50PC9rZXk+Cgk8aW50ZWdlcj4tODAwMDAwMDAw + MDA8L2ludGVnZXI+Cgk8a2V5PmFOZWdhdGl2ZUludDwva2V5PgoJPGludGVn + ZXI+LTU8L2ludGVnZXI+Cgk8a2V5PmFTdHJpbmc8L2tleT4KCTxzdHJpbmc+ + RG9vZGFoPC9zdHJpbmc+Cgk8a2V5PmFuRW1wdHlEaWN0PC9rZXk+Cgk8ZGlj + dC8+Cgk8a2V5PmFuRW1wdHlMaXN0PC9rZXk+Cgk8YXJyYXkvPgoJPGtleT5h + bkludDwva2V5PgoJPGludGVnZXI+NzI4PC9pbnRlZ2VyPgoJPGtleT5uZXN0 + ZWREYXRhPC9rZXk+Cgk8YXJyYXk+CgkJPGRhdGE+CgkJUEd4dmRITWdiMlln + WW1sdVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5k + VzVyCgkJUGdBQkFnTThiRzkwY3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJ + RFBHeHZkSE1nYjJZZ1ltbHVZWEo1CgkJSUdkMWJtcytBQUVDQXp4c2IzUnpJ + RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004Ykc5MGN5QnZaaUJpCgkJYVc1 + aGNua2daM1Z1YXo0QUFRSURQR3h2ZEhNZ2IyWWdZbWx1WVhKNUlHZDFibXMr + QUFFQ0F6eHNiM1J6CgkJSUc5bUlHSnBibUZ5ZVNCbmRXNXJQZ0FCQWdNOGJH + OTBjeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlECgkJUEd4dmRITWdiMlln + WW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09CgkJPC9kYXRhPgoJPC9hcnJheT4K + CTxrZXk+c29tZURhdGE8L2tleT4KCTxkYXRhPgoJUEdKcGJtRnllU0JuZFc1 + clBnPT0KCTwvZGF0YT4KCTxrZXk+c29tZU1vcmVEYXRhPC9rZXk+Cgk8ZGF0 + YT4KCVBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytBQUVDQXp4c2IzUnpJ + RzltSUdKcGJtRnllU0JuZFc1clBnQUJBZ004CgliRzkwY3lCdlppQmlhVzVo + Y25rZ1ozVnVhejRBQVFJRFBHeHZkSE1nYjJZZ1ltbHVZWEo1SUdkMWJtcytB + QUVDQXp4cwoJYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVyUGdBQkFnTThiRzkw + Y3lCdlppQmlhVzVoY25rZ1ozVnVhejRBQVFJRFBHeHYKCWRITWdiMllnWW1s + dVlYSjVJR2QxYm1zK0FBRUNBenhzYjNSeklHOW1JR0pwYm1GeWVTQm5kVzVy + UGdBQkFnTThiRzkwCgljeUJ2WmlCaWFXNWhjbmtnWjNWdWF6NEFBUUlEUEd4 + dmRITWdiMllnWW1sdVlYSjVJR2QxYm1zK0FBRUNBdz09Cgk8L2RhdGE+Cgk8 + a2V5PsOFYmVucmFhPC9rZXk+Cgk8c3RyaW5nPlRoYXQgd2FzIGEgdW5pY29k + ZSBrZXkuPC9zdHJpbmc+CjwvZGljdD4KPC9wbGlzdD4K'''), + plistlib.FMT_BINARY: binascii.a2b_base64(b''' + YnBsaXN0MDDfEBABAgMEBQYHCAkKCwwNDg8QERITFCgpLzAxMjM0NTc2OFdh + QmlnSW50WGFCaWdJbnQyVWFEYXRlVWFEaWN0VmFGbG9hdFVhTGlzdF8QD2FO + ZWdhdGl2ZUJpZ0ludFxhTmVnYXRpdmVJbnRXYVN0cmluZ1thbkVtcHR5RGlj + dFthbkVtcHR5TGlzdFVhbkludFpuZXN0ZWREYXRhWHNvbWVEYXRhXHNvbWVN + b3JlRGF0YWcAxQBiAGUAbgByAGEAYRN/////////1BQAAAAAAAAAAIAAAAAA + AAAsM0GcuX30AAAA1RUWFxgZGhscHR5bYUZhbHNlVmFsdWVaYVRydWVWYWx1 + ZV1hVW5pY29kZVZhbHVlXWFub3RoZXJTdHJpbmdaZGVlcGVyRGljdAgJawBN + AOQAcwBzAGkAZwAsACAATQBhAN9fEBU8aGVsbG8gJiAnaGknIHRoZXJlIT7T + HyAhIiMkUWFRYlFjEBEjQEBAAAAAAACjJSYnEAEQAlR0ZXh0Iz/gAAAAAAAA + pSorLCMtUUFRQhAMoyUmLhADE////+1foOAAE//////////7VkRvb2RhaNCg + EQLYoTZPEPo8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmlu + YXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBv + ZiBiaW5hcnkgZ3Vuaz4AAQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxs + b3RzIG9mIGJpbmFyeSBndW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4A + AQIDPGxvdHMgb2YgYmluYXJ5IGd1bms+AAECAzxsb3RzIG9mIGJpbmFyeSBn + dW5rPgABAgM8bG90cyBvZiBiaW5hcnkgZ3Vuaz4AAQIDTTxiaW5hcnkgZ3Vu + az5fEBdUaGF0IHdhcyBhIHVuaWNvZGUga2V5LgAIACsAMwA8AEIASABPAFUA + ZwB0AHwAiACUAJoApQCuALsAygDTAOQA7QD4AQQBDwEdASsBNgE3ATgBTwFn + AW4BcAFyAXQBdgF/AYMBhQGHAYwBlQGbAZ0BnwGhAaUBpwGwAbkBwAHBAcIB + xQHHAsQC0gAAAAAAAAIBAAAAAAAAADkAAAAAAAAAAAAAAAAAAALs'''), + 'KEYED_ARCHIVE': binascii.a2b_base64(b''' + YnBsaXN0MDDUAQIDBAUGHB1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVy + VCR0b3ASAAGGoKMHCA9VJG51bGzTCQoLDA0OVnB5dHlwZVYkY2xhc3NZTlMu + c3RyaW5nEAGAAl8QE0tleUFyY2hpdmUgVUlEIFRlc3TTEBESExQZWiRjbGFz + c25hbWVYJGNsYXNzZXNbJGNsYXNzaGludHNfEBdPQ19CdWlsdGluUHl0aG9u + VW5pY29kZaQVFhcYXxAXT0NfQnVpbHRpblB5dGhvblVuaWNvZGVfEBBPQ19Q + eXRob25Vbmljb2RlWE5TU3RyaW5nWE5TT2JqZWN0ohobXxAPT0NfUHl0aG9u + U3RyaW5nWE5TU3RyaW5nXxAPTlNLZXllZEFyY2hpdmVy0R4fVHJvb3SAAQAI + ABEAGgAjAC0AMgA3ADsAQQBIAE8AVgBgAGIAZAB6AIEAjACVAKEAuwDAANoA + 7QD2AP8BAgEUAR0BLwEyATcAAAAAAAACAQAAAAAAAAAgAAAAAAAAAAAAAAAA + AAABOQ=='''), +} + +XML_PLIST_WITH_ENTITY=b'''\ + + + ]> + + + A + &entity; + + +''' + +INVALID_BINARY_PLISTS = [ + ('too short data', + b'' + ), + ('too large offset_table_offset and offset_size = 1', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x2a' + ), + ('too large offset_table_offset and nonstandard offset_size', + b'\x00\x00\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x03\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x2c' + ), + ('integer overflow in offset_table_offset', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\xff\xff\xff\xff\xff\xff\xff\xff' + ), + ('too large top_object', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('integer overflow in top_object', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\xff\xff\xff\xff\xff\xff\xff\xff' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('too large num_objects and offset_size = 1', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\xff' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('too large num_objects and nonstandard offset_size', + b'\x00\x00\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x03\x01' + b'\x00\x00\x00\x00\x00\x00\x00\xff' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('extremally large num_objects (32 bit)', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x7f\xff\xff\xff' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('extremally large num_objects (64 bit)', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\xff\xff\xff\xff\xff' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('integer overflow in num_objects', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\xff\xff\xff\xff\xff\xff\xff\xff' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('offset_size = 0', + b'\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('ref_size = 0', + b'\xa1\x01\x00\x08\x0a' + b'\x00\x00\x00\x00\x00\x00\x01\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0b' + ), + ('too large offset', + b'\x00\x2a' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('integer overflow in offset', + b'\x00\xff\xff\xff\xff\xff\xff\xff\xff' + b'\x00\x00\x00\x00\x00\x00\x08\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x09' + ), + ('too large array size', + b'\xaf\x00\x01\xff\x00\x08\x0c' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0d' + ), + ('extremally large array size (32-bit)', + b'\xaf\x02\x7f\xff\xff\xff\x01\x00\x08\x0f' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x10' + ), + ('extremally large array size (64-bit)', + b'\xaf\x03\x00\x00\x00\xff\xff\xff\xff\xff\x01\x00\x08\x13' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x14' + ), + ('integer overflow in array size', + b'\xaf\x03\xff\xff\xff\xff\xff\xff\xff\xff\x01\x00\x08\x13' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x14' + ), + ('too large reference index', + b'\xa1\x02\x00\x08\x0a' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0b' + ), + ('integer overflow in reference index', + b'\xa1\xff\xff\xff\xff\xff\xff\xff\xff\x00\x08\x11' + b'\x00\x00\x00\x00\x00\x00\x01\x08' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x12' + ), + ('too large bytes size', + b'\x4f\x00\x23\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0c' + ), + ('extremally large bytes size (32-bit)', + b'\x4f\x02\x7f\xff\xff\xff\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0f' + ), + ('extremally large bytes size (64-bit)', + b'\x4f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x13' + ), + ('integer overflow in bytes size', + b'\x4f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x13' + ), + ('too large ASCII size', + b'\x5f\x00\x23\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0c' + ), + ('extremally large ASCII size (32-bit)', + b'\x5f\x02\x7f\xff\xff\xff\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0f' + ), + ('extremally large ASCII size (64-bit)', + b'\x5f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x13' + ), + ('integer overflow in ASCII size', + b'\x5f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x41\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x13' + ), + ('invalid ASCII', + b'\x51\xff\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0a' + ), + ('too large UTF-16 size', + b'\x6f\x00\x13\x20\xac\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0e' + ), + ('extremally large UTF-16 size (32-bit)', + b'\x6f\x02\x4f\xff\xff\xff\x20\xac\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x11' + ), + ('extremally large UTF-16 size (64-bit)', + b'\x6f\x03\x00\x00\x00\xff\xff\xff\xff\xff\x20\xac\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x15' + ), + ('integer overflow in UTF-16 size', + b'\x6f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x20\xac\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x15' + ), + ('invalid UTF-16', + b'\x61\xd8\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0b' + ), + ('non-hashable key', + b'\xd1\x01\x01\xa0\x08\x0b' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x02' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x0c' + ), + ('too large datetime (datetime overflow)', + b'\x33\x42\x50\x00\x00\x00\x00\x00\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x11' + ), + ('too large datetime (timedelta overflow)', + b'\x33\x42\xe0\x00\x00\x00\x00\x00\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x11' + ), + ('invalid datetime (Infinity)', + b'\x33\x7f\xf0\x00\x00\x00\x00\x00\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x11' + ), + ('invalid datetime (NaN)', + b'\x33\x7f\xf8\x00\x00\x00\x00\x00\x00\x08' + b'\x00\x00\x00\x00\x00\x00\x01\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x11' + ), +] + + +class TestPlistlib(unittest.TestCase): + + def tearDown(self): + try: + os.unlink(os_helper.TESTFN) + except: + pass + + def _create(self, fmt=None): + pl = dict( + aString="Doodah", + aList=["A", "B", 12, 32.5, [1, 2, 3]], + aFloat = 0.5, + anInt = 728, + aBigInt = 2 ** 63 - 44, + aBigInt2 = 2 ** 63 + 44, + aNegativeInt = -5, + aNegativeBigInt = -80000000000, + aDict=dict( + anotherString="", + aUnicodeValue='M\xe4ssig, Ma\xdf', + aTrueValue=True, + aFalseValue=False, + deeperDict=dict(a=17, b=32.5, c=[1, 2, "text"]), + ), + someData = b"", + someMoreData = b"\0\1\2\3" * 10, + nestedData = [b"\0\1\2\3" * 10], + aDate = datetime.datetime(2004, 10, 26, 10, 33, 33), + anEmptyDict = dict(), + anEmptyList = list() + ) + pl['\xc5benraa'] = "That was a unicode key." + return pl + + def test_create(self): + pl = self._create() + self.assertEqual(pl["aString"], "Doodah") + self.assertEqual(pl["aDict"]["aFalseValue"], False) + + def test_io(self): + pl = self._create() + with open(os_helper.TESTFN, 'wb') as fp: + plistlib.dump(pl, fp) + + with open(os_helper.TESTFN, 'rb') as fp: + pl2 = plistlib.load(fp) + + self.assertEqual(dict(pl), dict(pl2)) + + self.assertRaises(AttributeError, plistlib.dump, pl, 'filename') + self.assertRaises(AttributeError, plistlib.load, 'filename') + + def test_invalid_type(self): + pl = [ object() ] + + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt) + + def test_invalid_uid(self): + with self.assertRaises(TypeError): + UID("not an int") + with self.assertRaises(ValueError): + UID(2 ** 64) + with self.assertRaises(ValueError): + UID(-19) + + def test_int(self): + for pl in [0, 2**8-1, 2**8, 2**16-1, 2**16, 2**32-1, 2**32, + 2**63-1, 2**64-1, 1, -2**63]: + for fmt in ALL_FORMATS: + with self.subTest(pl=pl, fmt=fmt): + data = plistlib.dumps(pl, fmt=fmt) + pl2 = plistlib.loads(data) + self.assertIsInstance(pl2, int) + self.assertEqual(pl, pl2) + data2 = plistlib.dumps(pl2, fmt=fmt) + self.assertEqual(data, data2) + + for fmt in ALL_FORMATS: + for pl in (2 ** 64 + 1, 2 ** 127-1, -2**64, -2 ** 127): + with self.subTest(pl=pl, fmt=fmt): + self.assertRaises(OverflowError, plistlib.dumps, + pl, fmt=fmt) + + def test_bytearray(self): + for pl in (b'', b"\0\1\2\3" * 10): + for fmt in ALL_FORMATS: + with self.subTest(pl=pl, fmt=fmt): + data = plistlib.dumps(bytearray(pl), fmt=fmt) + pl2 = plistlib.loads(data) + self.assertIsInstance(pl2, bytes) + self.assertEqual(pl2, pl) + data2 = plistlib.dumps(pl2, fmt=fmt) + self.assertEqual(data, data2) + + def test_bytes(self): + pl = self._create() + data = plistlib.dumps(pl) + pl2 = plistlib.loads(data) + self.assertEqual(dict(pl), dict(pl2)) + data2 = plistlib.dumps(pl2) + self.assertEqual(data, data2) + + def test_indentation_array(self): + data = [[[[[[[[{'test': b'aaaaaa'}]]]]]]]] + self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) + + def test_indentation_dict(self): + data = {'1': {'2': {'3': {'4': {'5': {'6': {'7': {'8': {'9': b'aaaaaa'}}}}}}}}} + self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) + + def test_indentation_dict_mix(self): + data = {'1': {'2': [{'3': [[[[[{'test': b'aaaaaa'}]]]]]}]}} + self.assertEqual(plistlib.loads(plistlib.dumps(data)), data) + + def test_uid(self): + data = UID(1) + self.assertEqual(plistlib.loads(plistlib.dumps(data, fmt=plistlib.FMT_BINARY)), data) + dict_data = { + 'uid0': UID(0), + 'uid2': UID(2), + 'uid8': UID(2 ** 8), + 'uid16': UID(2 ** 16), + 'uid32': UID(2 ** 32), + 'uid63': UID(2 ** 63) + } + self.assertEqual(plistlib.loads(plistlib.dumps(dict_data, fmt=plistlib.FMT_BINARY)), dict_data) + + def test_uid_data(self): + uid = UID(1) + self.assertEqual(uid.data, 1) + + def test_uid_eq(self): + self.assertEqual(UID(1), UID(1)) + self.assertNotEqual(UID(1), UID(2)) + self.assertNotEqual(UID(1), "not uid") + + def test_uid_hash(self): + self.assertEqual(hash(UID(1)), hash(UID(1))) + + def test_uid_repr(self): + self.assertEqual(repr(UID(1)), "UID(1)") + + def test_uid_index(self): + self.assertEqual(operator.index(UID(1)), 1) + + def test_uid_pickle(self): + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + self.assertEqual(pickle.loads(pickle.dumps(UID(19), protocol=proto)), UID(19)) + + def test_uid_copy(self): + self.assertEqual(copy.copy(UID(1)), UID(1)) + self.assertEqual(copy.deepcopy(UID(1)), UID(1)) + + def test_appleformatting(self): + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + pl = plistlib.loads(TESTDATA[fmt]) + data = plistlib.dumps(pl, fmt=fmt) + self.assertEqual(data, TESTDATA[fmt], + "generated data was not identical to Apple's output") + + + def test_appleformattingfromliteral(self): + self.maxDiff = None + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + pl = self._create(fmt=fmt) + pl2 = plistlib.loads(TESTDATA[fmt], fmt=fmt) + self.assertEqual(dict(pl), dict(pl2), + "generated data was not identical to Apple's output") + pl2 = plistlib.loads(TESTDATA[fmt]) + self.assertEqual(dict(pl), dict(pl2), + "generated data was not identical to Apple's output") + + def test_bytesio(self): + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + b = BytesIO() + pl = self._create(fmt=fmt) + plistlib.dump(pl, b, fmt=fmt) + pl2 = plistlib.load(BytesIO(b.getvalue()), fmt=fmt) + self.assertEqual(dict(pl), dict(pl2)) + pl2 = plistlib.load(BytesIO(b.getvalue())) + self.assertEqual(dict(pl), dict(pl2)) + + def test_keysort_bytesio(self): + pl = collections.OrderedDict() + pl['b'] = 1 + pl['a'] = 2 + pl['c'] = 3 + + for fmt in ALL_FORMATS: + for sort_keys in (False, True): + with self.subTest(fmt=fmt, sort_keys=sort_keys): + b = BytesIO() + + plistlib.dump(pl, b, fmt=fmt, sort_keys=sort_keys) + pl2 = plistlib.load(BytesIO(b.getvalue()), + dict_type=collections.OrderedDict) + + self.assertEqual(dict(pl), dict(pl2)) + if sort_keys: + self.assertEqual(list(pl2.keys()), ['a', 'b', 'c']) + else: + self.assertEqual(list(pl2.keys()), ['b', 'a', 'c']) + + def test_keysort(self): + pl = collections.OrderedDict() + pl['b'] = 1 + pl['a'] = 2 + pl['c'] = 3 + + for fmt in ALL_FORMATS: + for sort_keys in (False, True): + with self.subTest(fmt=fmt, sort_keys=sort_keys): + data = plistlib.dumps(pl, fmt=fmt, sort_keys=sort_keys) + pl2 = plistlib.loads(data, dict_type=collections.OrderedDict) + + self.assertEqual(dict(pl), dict(pl2)) + if sort_keys: + self.assertEqual(list(pl2.keys()), ['a', 'b', 'c']) + else: + self.assertEqual(list(pl2.keys()), ['b', 'a', 'c']) + + def test_keys_no_string(self): + pl = { 42: 'aNumber' } + + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + self.assertRaises(TypeError, plistlib.dumps, pl, fmt=fmt) + + b = BytesIO() + self.assertRaises(TypeError, plistlib.dump, pl, b, fmt=fmt) + + def test_skipkeys(self): + pl = { + 42: 'aNumber', + 'snake': 'aWord', + } + + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + data = plistlib.dumps( + pl, fmt=fmt, skipkeys=True, sort_keys=False) + + pl2 = plistlib.loads(data) + self.assertEqual(pl2, {'snake': 'aWord'}) + + fp = BytesIO() + plistlib.dump( + pl, fp, fmt=fmt, skipkeys=True, sort_keys=False) + data = fp.getvalue() + pl2 = plistlib.loads(fp.getvalue()) + self.assertEqual(pl2, {'snake': 'aWord'}) + + def test_tuple_members(self): + pl = { + 'first': (1, 2), + 'second': (1, 2), + 'third': (3, 4), + } + + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + data = plistlib.dumps(pl, fmt=fmt) + pl2 = plistlib.loads(data) + self.assertEqual(pl2, { + 'first': [1, 2], + 'second': [1, 2], + 'third': [3, 4], + }) + if fmt != plistlib.FMT_BINARY: + self.assertIsNot(pl2['first'], pl2['second']) + + def test_list_members(self): + pl = { + 'first': [1, 2], + 'second': [1, 2], + 'third': [3, 4], + } + + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + data = plistlib.dumps(pl, fmt=fmt) + pl2 = plistlib.loads(data) + self.assertEqual(pl2, { + 'first': [1, 2], + 'second': [1, 2], + 'third': [3, 4], + }) + self.assertIsNot(pl2['first'], pl2['second']) + + def test_dict_members(self): + pl = { + 'first': {'a': 1}, + 'second': {'a': 1}, + 'third': {'b': 2 }, + } + + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + data = plistlib.dumps(pl, fmt=fmt) + pl2 = plistlib.loads(data) + self.assertEqual(pl2, { + 'first': {'a': 1}, + 'second': {'a': 1}, + 'third': {'b': 2 }, + }) + self.assertIsNot(pl2['first'], pl2['second']) + + def test_controlcharacters(self): + for i in range(128): + c = chr(i) + testString = "string containing %s" % c + if i >= 32 or c in "\r\n\t": + # \r, \n and \t are the only legal control chars in XML + data = plistlib.dumps(testString, fmt=plistlib.FMT_XML) + if c != "\r": + self.assertEqual(plistlib.loads(data), testString) + else: + with self.assertRaises(ValueError): + plistlib.dumps(testString, fmt=plistlib.FMT_XML) + plistlib.dumps(testString, fmt=plistlib.FMT_BINARY) + + def test_non_bmp_characters(self): + pl = {'python': '\U0001f40d'} + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + data = plistlib.dumps(pl, fmt=fmt) + self.assertEqual(plistlib.loads(data), pl) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_lone_surrogates(self): + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + with self.assertRaises(UnicodeEncodeError): + plistlib.dumps('\ud8ff', fmt=fmt) + with self.assertRaises(UnicodeEncodeError): + plistlib.dumps('\udcff', fmt=fmt) + + def test_nondictroot(self): + for fmt in ALL_FORMATS: + with self.subTest(fmt=fmt): + test1 = "abc" + test2 = [1, 2, 3, "abc"] + result1 = plistlib.loads(plistlib.dumps(test1, fmt=fmt)) + result2 = plistlib.loads(plistlib.dumps(test2, fmt=fmt)) + self.assertEqual(test1, result1) + self.assertEqual(test2, result2) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_invalidarray(self): + for i in ["key inside an array", + "key inside an array23", + "key inside an array3"]: + self.assertRaises(ValueError, plistlib.loads, + ("%s"%i).encode()) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_invaliddict(self): + for i in ["kcompound key", + "single key", + "missing key", + "k1v15.3" + "k1k2double key"]: + self.assertRaises(ValueError, plistlib.loads, + ("%s"%i).encode()) + self.assertRaises(ValueError, plistlib.loads, + ("%s"%i).encode()) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_invalidinteger(self): + self.assertRaises(ValueError, plistlib.loads, + b"not integer") + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_invalidreal(self): + self.assertRaises(ValueError, plistlib.loads, + b"not real") + + def test_integer_notations(self): + pl = b"456" + value = plistlib.loads(pl) + self.assertEqual(value, 456) + + pl = b"0xa" + value = plistlib.loads(pl) + self.assertEqual(value, 10) + + pl = b"0123" + value = plistlib.loads(pl) + self.assertEqual(value, 123) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_xml_encodings(self): + base = TESTDATA[plistlib.FMT_XML] + + for xml_encoding, encoding, bom in [ + (b'utf-8', 'utf-8', codecs.BOM_UTF8), + (b'utf-16', 'utf-16-le', codecs.BOM_UTF16_LE), + (b'utf-16', 'utf-16-be', codecs.BOM_UTF16_BE), + # Expat does not support UTF-32 + #(b'utf-32', 'utf-32-le', codecs.BOM_UTF32_LE), + #(b'utf-32', 'utf-32-be', codecs.BOM_UTF32_BE), + ]: + + pl = self._create(fmt=plistlib.FMT_XML) + with self.subTest(encoding=encoding): + data = base.replace(b'UTF-8', xml_encoding) + data = bom + data.decode('utf-8').encode(encoding) + pl2 = plistlib.loads(data) + self.assertEqual(dict(pl), dict(pl2)) + + def test_dump_invalid_format(self): + with self.assertRaises(ValueError): + plistlib.dumps({}, fmt="blah") + + def test_load_invalid_file(self): + with self.assertRaises(plistlib.InvalidFileException): + plistlib.loads(b"these are not plist file contents") + + def test_modified_uid_negative(self): + neg_uid = UID(1) + neg_uid.data = -1 # dodge the negative check in the constructor + with self.assertRaises(ValueError): + plistlib.dumps(neg_uid, fmt=plistlib.FMT_BINARY) + + def test_modified_uid_huge(self): + huge_uid = UID(1) + huge_uid.data = 2 ** 64 # dodge the size check in the constructor + with self.assertRaises(OverflowError): + plistlib.dumps(huge_uid, fmt=plistlib.FMT_BINARY) + + # TODO: RUSTPYTHON + @unittest.expectedFailure + def test_xml_plist_with_entity_decl(self): + with self.assertRaisesRegex(plistlib.InvalidFileException, + "XML entity declarations are not supported"): + plistlib.loads(XML_PLIST_WITH_ENTITY, fmt=plistlib.FMT_XML) + + +class TestBinaryPlistlib(unittest.TestCase): + + @staticmethod + def decode(*objects, offset_size=1, ref_size=1): + data = [b'bplist00'] + offset = 8 + offsets = [] + for x in objects: + offsets.append(offset.to_bytes(offset_size, 'big')) + data.append(x) + offset += len(x) + tail = struct.pack('>6xBBQQQ', offset_size, ref_size, + len(objects), 0, offset) + data.extend(offsets) + data.append(tail) + return plistlib.loads(b''.join(data), fmt=plistlib.FMT_BINARY) + + def test_nonstandard_refs_size(self): + # Issue #21538: Refs and offsets are 24-bit integers + data = (b'bplist00' + b'\xd1\x00\x00\x01\x00\x00\x02QaQb' + b'\x00\x00\x08\x00\x00\x0f\x00\x00\x11' + b'\x00\x00\x00\x00\x00\x00' + b'\x03\x03' + b'\x00\x00\x00\x00\x00\x00\x00\x03' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x13') + self.assertEqual(plistlib.loads(data), {'a': 'b'}) + + def test_dump_duplicates(self): + # Test effectiveness of saving duplicated objects + for x in (None, False, True, 12345, 123.45, 'abcde', 'абвгд', b'abcde', + datetime.datetime(2004, 10, 26, 10, 33, 33), + bytearray(b'abcde'), [12, 345], (12, 345), {'12': 345}): + with self.subTest(x=x): + data = plistlib.dumps([x]*1000, fmt=plistlib.FMT_BINARY) + self.assertLess(len(data), 1100, repr(data)) + + def test_identity(self): + for x in (None, False, True, 12345, 123.45, 'abcde', b'abcde', + datetime.datetime(2004, 10, 26, 10, 33, 33), + bytearray(b'abcde'), [12, 345], (12, 345), {'12': 345}): + with self.subTest(x=x): + data = plistlib.dumps([x]*2, fmt=plistlib.FMT_BINARY) + a, b = plistlib.loads(data) + if isinstance(x, tuple): + x = list(x) + self.assertEqual(a, x) + self.assertEqual(b, x) + self.assertIs(a, b) + + def test_cycles(self): + # recursive list + a = [] + a.append(a) + b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) + self.assertIs(b[0], b) + # recursive tuple + a = ([],) + a[0].append(a) + b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) + self.assertIs(b[0][0], b) + # recursive dict + a = {} + a['x'] = a + b = plistlib.loads(plistlib.dumps(a, fmt=plistlib.FMT_BINARY)) + self.assertIs(b['x'], b) + + def test_deep_nesting(self): + for N in [300, 100000]: + chunks = [b'\xa1' + (i + 1).to_bytes(4, 'big') for i in range(N)] + try: + result = self.decode(*chunks, b'\x54seed', offset_size=4, ref_size=4) + except RecursionError: + pass + else: + for i in range(N): + self.assertIsInstance(result, list) + self.assertEqual(len(result), 1) + result = result[0] + self.assertEqual(result, 'seed') + + def test_large_timestamp(self): + # Issue #26709: 32-bit timestamp out of range + for ts in -2**31-1, 2**31: + with self.subTest(ts=ts): + d = (datetime.datetime.utcfromtimestamp(0) + + datetime.timedelta(seconds=ts)) + data = plistlib.dumps(d, fmt=plistlib.FMT_BINARY) + self.assertEqual(plistlib.loads(data), d) + + def test_load_singletons(self): + self.assertIs(self.decode(b'\x00'), None) + self.assertIs(self.decode(b'\x08'), False) + self.assertIs(self.decode(b'\x09'), True) + self.assertEqual(self.decode(b'\x0f'), b'') + + def test_load_int(self): + self.assertEqual(self.decode(b'\x10\x00'), 0) + self.assertEqual(self.decode(b'\x10\xfe'), 0xfe) + self.assertEqual(self.decode(b'\x11\xfe\xdc'), 0xfedc) + self.assertEqual(self.decode(b'\x12\xfe\xdc\xba\x98'), 0xfedcba98) + self.assertEqual(self.decode(b'\x13\x01\x23\x45\x67\x89\xab\xcd\xef'), + 0x0123456789abcdef) + self.assertEqual(self.decode(b'\x13\xfe\xdc\xba\x98\x76\x54\x32\x10'), + -0x123456789abcdf0) + + def test_unsupported(self): + unsupported = [*range(1, 8), *range(10, 15), + 0x20, 0x21, *range(0x24, 0x33), *range(0x34, 0x40)] + for i in [0x70, 0x90, 0xb0, 0xc0, 0xe0, 0xf0]: + unsupported.extend(i + j for j in range(16)) + for token in unsupported: + with self.subTest(f'token {token:02x}'): + with self.assertRaises(plistlib.InvalidFileException): + self.decode(bytes([token]) + b'\x00'*16) + + def test_invalid_binary(self): + for name, data in INVALID_BINARY_PLISTS: + with self.subTest(name): + with self.assertRaises(plistlib.InvalidFileException): + plistlib.loads(b'bplist00' + data, fmt=plistlib.FMT_BINARY) + + +class TestKeyedArchive(unittest.TestCase): + def test_keyed_archive_data(self): + # This is the structure of a NSKeyedArchive packed plist + data = { + '$version': 100000, + '$objects': [ + '$null', { + 'pytype': 1, + '$class': UID(2), + 'NS.string': 'KeyArchive UID Test' + }, + { + '$classname': 'OC_BuiltinPythonUnicode', + '$classes': [ + 'OC_BuiltinPythonUnicode', + 'OC_PythonUnicode', + 'NSString', + 'NSObject' + ], + '$classhints': [ + 'OC_PythonString', 'NSString' + ] + } + ], + '$archiver': 'NSKeyedArchiver', + '$top': { + 'root': UID(1) + } + } + self.assertEqual(plistlib.loads(TESTDATA["KEYED_ARCHIVE"]), data) + + +class MiscTestCase(unittest.TestCase): + def test__all__(self): + not_exported = {"PlistFormat", "PLISTHEADER"} + support.check__all__(self, plistlib, not_exported=not_exported) + + +if __name__ == '__main__': + unittest.main() diff --git a/stdlib/src/binascii.rs b/stdlib/src/binascii.rs index 3fa664262..87ac684d5 100644 --- a/stdlib/src/binascii.rs +++ b/stdlib/src/binascii.rs @@ -145,19 +145,44 @@ mod decl { #[pyfunction] fn a2b_base64(s: ArgAsciiBuffer, vm: &VirtualMachine) -> PyResult> { + #[rustfmt::skip] + const BASE64_TABLE: [i8; 256] = [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, /* Note PAD->0 */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1, + + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + ]; + s.with_ref(|b| { - let mut buf; - let b = if memchr::memchr(b'\n', b).is_some() { - buf = b.to_vec(); - buf.retain(|c| *c != b'\n'); - &buf + let decoded = if b.len() % 4 == 0 { + base64::decode(b) } else { - b + Err(base64::DecodeError::InvalidLength) }; - if b.len() % 4 != 0 { - return Err(base64::DecodeError::InvalidLength); - } - base64::decode(b) + decoded.or_else(|_| { + let buf: Vec<_> = b + .iter() + .copied() + .filter(|&c| BASE64_TABLE[c as usize] != -1) + .collect(); + if buf.len() % 4 != 0 { + return Err(base64::DecodeError::InvalidLength); + } + base64::decode(&buf) + }) }) .map_err(|err| new_binascii_error(format!("error decoding base64: {}", err), vm)) }