mirror of
https://github.com/corpnewt/gibMacOS.git
synced 2025-01-09 19:07:45 +01:00
216 lines
8.1 KiB
Python
216 lines
8.1 KiB
Python
### ###
|
|
# Imports #
|
|
### ###
|
|
|
|
import datetime
|
|
from io import BytesIO
|
|
import os
|
|
import plistlib
|
|
import struct
|
|
import sys
|
|
|
|
if sys.version_info < (3,0):
|
|
# Force use of StringIO instead of cStringIO as the latter
|
|
# has issues with Unicode strings
|
|
from StringIO import StringIO
|
|
|
|
try:
|
|
FMT_XML = plistlib.FMT_XML
|
|
except:
|
|
FMT_XML = None
|
|
|
|
### ###
|
|
# Helper Methods #
|
|
### ###
|
|
|
|
def _check_py3():
|
|
return True if sys.version_info >= (3, 0) else False
|
|
|
|
def _is_binary(fp):
|
|
if isinstance(fp, _get_inst()):
|
|
return fp.startswith(b"bplist00")
|
|
header = fp.read(32)
|
|
fp.seek(0)
|
|
return header[:8] == b'bplist00'
|
|
|
|
def _get_inst():
|
|
if _check_py3():
|
|
return (str)
|
|
else:
|
|
return (str, unicode)
|
|
|
|
### ###
|
|
# Deprecated Functions - Remapped #
|
|
### ###
|
|
|
|
def readPlist(pathOrFile):
|
|
if not isinstance(pathOrFile, _get_inst()):
|
|
return load(pathOrFile)
|
|
with open(pathOrFile, "rb") as f:
|
|
return load(f)
|
|
|
|
def writePlist(value, pathOrFile):
|
|
if not isinstance(pathOrFile, _get_inst()):
|
|
return dump(value, pathOrFile, fmt=FMT_XML, sort_keys=True, skipkeys=False)
|
|
with open(pathOrFile, "wb") as f:
|
|
return dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
|
|
|
|
### ###
|
|
# Remapped Functions #
|
|
### ###
|
|
|
|
def load(fp, fmt=None, use_builtin_types=True, dict_type=dict):
|
|
if _check_py3():
|
|
return plistlib.load(fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type)
|
|
elif not _is_binary(fp):
|
|
return plistlib.readPlist(fp)
|
|
else:
|
|
return readBinaryPlistFile(fp)
|
|
|
|
def loads(value, fmt=None, use_builtin_types=True, dict_type=dict):
|
|
if _check_py3():
|
|
# Requires fp to be a BytesIO wrapper around a bytes object
|
|
if isinstance(value, _get_inst()):
|
|
# If it's a string - encode it
|
|
value = value.encode()
|
|
# Load it
|
|
return plistlib.load(BytesIO(value), fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type)
|
|
else:
|
|
if _is_binary(value):
|
|
# Has the proper header to be a binary plist
|
|
return readBinaryPlistFile(BytesIO(value))
|
|
else:
|
|
# Is not binary - assume a string - and try to load
|
|
# We avoid using readPlistFromString() as that uses
|
|
# cStringIO and fails when Unicode strings are detected
|
|
# Don't subclass - keep the parser local
|
|
from xml.parsers.expat import ParserCreate
|
|
# Create a new PlistParser object - then we need to set up
|
|
# the values and parse.
|
|
p = plistlib.PlistParser()
|
|
parser = ParserCreate()
|
|
parser.StartElementHandler = p.handleBeginElement
|
|
parser.EndElementHandler = p.handleEndElement
|
|
parser.CharacterDataHandler = p.handleData
|
|
if isinstance(value, unicode):
|
|
# Encode unicode -> string; use utf-8 for safety
|
|
value = value.encode("utf-8")
|
|
# Parse the string
|
|
parser.Parse(value, 1)
|
|
return p.root
|
|
|
|
rootObject = p.parse(s)
|
|
return rootObject
|
|
|
|
def dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False):
|
|
if _check_py3():
|
|
plistlib.dump(value, fp, fmt=fmt, sort_keys=sort_keys, skipkeys=skipkeys)
|
|
else:
|
|
plistlib.writePlist(value, fp)
|
|
|
|
def dumps(value, fmt=FMT_XML, skipkeys=False):
|
|
if _check_py3():
|
|
return plistlib.dumps(value, fmt=fmt, skipkeys=skipkeys).decode("utf-8")
|
|
else:
|
|
# We avoid using writePlistToString() as that uses
|
|
# cStringIO and fails when Unicode strings are detected
|
|
f = StringIO()
|
|
plistlib.writePlist(value, f)
|
|
return f.getvalue()
|
|
|
|
### ###
|
|
# Binary Plist Stuff For Py2 #
|
|
### ###
|
|
|
|
# timestamp 0 of binary plists corresponds to 1/1/2001 (year of Mac OS X 10.0), instead of 1/1/1970.
|
|
MAC_OS_X_TIME_OFFSET = (31 * 365 + 8) * 86400
|
|
|
|
class InvalidFileException(ValueError):
|
|
def __str__(self):
|
|
return "Invalid file"
|
|
def __unicode__(self):
|
|
return "Invalid file"
|
|
|
|
def readBinaryPlistFile(in_file):
|
|
"""
|
|
Read a binary plist file, following the description of the binary format: http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c
|
|
Raise InvalidFileException in case of error, otherwise return the root object, as usual
|
|
|
|
Original patch diffed here: https://bugs.python.org/issue14455
|
|
"""
|
|
in_file.seek(-32, os.SEEK_END)
|
|
trailer = in_file.read(32)
|
|
if len(trailer) != 32:
|
|
return InvalidFileException()
|
|
offset_size, ref_size, num_objects, top_object, offset_table_offset = struct.unpack('>6xBB4xL4xL4xL', trailer)
|
|
in_file.seek(offset_table_offset)
|
|
object_offsets = []
|
|
offset_format = '>' + {1: 'B', 2: 'H', 4: 'L', 8: 'Q', }[offset_size] * num_objects
|
|
ref_format = {1: 'B', 2: 'H', 4: 'L', 8: 'Q', }[ref_size]
|
|
int_format = {0: (1, '>B'), 1: (2, '>H'), 2: (4, '>L'), 3: (8, '>Q'), }
|
|
object_offsets = struct.unpack(offset_format, in_file.read(offset_size * num_objects))
|
|
def getSize(token_l):
|
|
""" return the size of the next object."""
|
|
if token_l == 0xF:
|
|
m = ord(in_file.read(1)) & 0x3
|
|
s, f = int_format[m]
|
|
return struct.unpack(f, in_file.read(s))[0]
|
|
return token_l
|
|
def readNextObject(offset):
|
|
""" read the object at offset. May recursively read sub-objects (content of an array/dict/set) """
|
|
in_file.seek(offset)
|
|
token = in_file.read(1)
|
|
token_h, token_l = ord(token) & 0xF0, ord(token) & 0x0F #high and low parts
|
|
if token == '\x00':
|
|
return None
|
|
elif token == '\x08':
|
|
return False
|
|
elif token == '\x09':
|
|
return True
|
|
elif token == '\x0f':
|
|
return ''
|
|
elif token_h == 0x10: #int
|
|
result = 0
|
|
for k in xrange((2 << token_l) - 1):
|
|
result = (result << 8) + ord(in_file.read(1))
|
|
return result
|
|
elif token_h == 0x20: #real
|
|
if token_l == 2:
|
|
return struct.unpack('>f', in_file.read(4))[0]
|
|
elif token_l == 3:
|
|
return struct.unpack('>d', in_file.read(8))[0]
|
|
elif token_h == 0x30: #date
|
|
f = struct.unpack('>d', in_file.read(8))[0]
|
|
return datetime.datetime.utcfromtimestamp(f + MAC_OS_X_TIME_OFFSET)
|
|
elif token_h == 0x40: #data
|
|
s = getSize(token_l)
|
|
return plistlib.Data(in_file.read(s))
|
|
elif token_h == 0x50: #ascii string
|
|
s = getSize(token_l)
|
|
return in_file.read(s)
|
|
elif token_h == 0x60: #unicode string
|
|
s = getSize(token_l)
|
|
return in_file.read(s * 2).decode('utf-16be')
|
|
elif token_h == 0x80: #uid
|
|
return in_file.read(token_l + 1)
|
|
elif token_h == 0xA0: #array
|
|
s = getSize(token_l)
|
|
obj_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size))
|
|
return map(lambda x: readNextObject(object_offsets[x]), obj_refs)
|
|
elif token_h == 0xC0: #set
|
|
s = getSize(token_l)
|
|
obj_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size))
|
|
return set(map(lambda x: readNextObject(object_offsets[x]), obj_refs))
|
|
elif token_h == 0xD0: #dict
|
|
result = {}
|
|
s = getSize(token_l)
|
|
key_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size))
|
|
obj_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size))
|
|
for k, o in zip(key_refs, obj_refs):
|
|
key = readNextObject(object_offsets[k])
|
|
obj = readNextObject(object_offsets[o])
|
|
result[key] = obj
|
|
return result
|
|
raise InvalidFileException()
|
|
return readNextObject(object_offsets[top_object])
|