[3.6] bpo-32072: Fix issues with binary plists. (GH-4455) by miss-islington · Pull Request #4654 · python/cpython
_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}
_undefined = object()
class _BinaryPlistParser: """ Read or write a binary plist file, following the description of the binary
except (OSError, IndexError, struct.error, OverflowError, UnicodeDecodeError):
def _read_object(self, offset): def _read_object(self, ref): """ read the object at offset. read the object by reference.
May recursively read sub-objects (content of an array/dict/set) """ result = self._objects[ref] if result is not _undefined: return result
offset = self._object_offsets[ref] self._fp.seek(offset) token = self._fp.read(1)[0] tokenH, tokenL = token & 0xF0, token & 0x0F
if token == 0x00: return None result = None
elif token == 0x08: return False result = False
elif token == 0x09: return True result = True
# The referenced source code also mentions URL (0x0c, 0x0d) and # UUID (0x0e), but neither can be generated using the Cocoa libraries.
elif token == 0x0f: return b'' result = b''
elif tokenH == 0x10: # int return int.from_bytes(self._fp.read(1 << tokenL), 'big', signed=tokenL >= 3) result = int.from_bytes(self._fp.read(1 << tokenL), 'big', signed=tokenL >= 3)
elif token == 0x22: # real return struct.unpack('>f', self._fp.read(4))[0] result = struct.unpack('>f', self._fp.read(4))[0]
elif token == 0x23: # real return struct.unpack('>d', self._fp.read(8))[0] result = struct.unpack('>d', self._fp.read(8))[0]
elif token == 0x33: # date f = struct.unpack('>d', self._fp.read(8))[0] # timestamp 0 of binary plists corresponds to 1/1/2001 # (year of Mac OS X 10.0), instead of 1/1/1970. return datetime.datetime(2001, 1, 1) + datetime.timedelta(seconds=f) result = (datetime.datetime(2001, 1, 1) + datetime.timedelta(seconds=f))
elif tokenH == 0x40: # data s = self._get_size(tokenL) if self._use_builtin_types: return self._fp.read(s) result = self._fp.read(s) else: return Data(self._fp.read(s)) result = Data(self._fp.read(s))
elif tokenH == 0x50: # ascii string s = self._get_size(tokenL) result = self._fp.read(s).decode('ascii') return result result = result
elif tokenH == 0x60: # unicode string s = self._get_size(tokenL) return self._fp.read(s * 2).decode('utf-16be') result = self._fp.read(s * 2).decode('utf-16be')
# tokenH == 0x80 is documented as 'UID' and appears to be used for # keyed-archiving, not in plists.
elif tokenH == 0xA0: # array s = self._get_size(tokenL) obj_refs = self._read_refs(s) return [self._read_object(self._object_offsets[x]) for x in obj_refs] result = [] self._objects[ref] = result result.extend(self._read_object(x) for x in obj_refs)
# tokenH == 0xB0 is documented as 'ordset', but is not actually # implemented in the Apple reference code.
raise InvalidFileException() else: raise InvalidFileException()
self._objects[ref] = result return result
def _count_to_size(count): if count < 1 << 8:
_scalars = (str, int, float, datetime.datetime, bytes)
class _BinaryPlistWriter (object): def __init__(self, fp, sort_keys, skipkeys): self._fp = fp
elif isinstance(value, Data): if (type(value.data), value.data) in self._objtable: return
elif id(value) in self._objidtable: return
# Add to objectreference map refnum = len(self._objlist) self._objlist.append(value) try: if isinstance(value, Data): self._objtable[(type(value.data), value.data)] = refnum else: self._objtable[(type(value), value)] = refnum except TypeError: if isinstance(value, _scalars): self._objtable[(type(value), value)] = refnum elif isinstance(value, Data): self._objtable[(type(value.data), value.data)] = refnum else: self._objidtable[id(value)] = refnum
# And finally recurse into containers
def _getrefnum(self, value): try: if isinstance(value, Data): return self._objtable[(type(value.data), value.data)] else: return self._objtable[(type(value), value)] except TypeError: if isinstance(value, _scalars): return self._objtable[(type(value), value)] elif isinstance(value, Data): return self._objtable[(type(value.data), value.data)] else: return self._objidtable[id(value)]
def _write_size(self, token, size):