xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/plistlib.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Workerr"""plistlib.py -- a tool to generate and parse MacOSX .plist files.
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard WorkerThe property list (.plist) file format is a simple XML pickle supporting
4*cda5da8dSAndroid Build Coastguard Workerbasic object types, like dictionaries, lists, numbers and strings.
5*cda5da8dSAndroid Build Coastguard WorkerUsually the top level object is a dictionary.
6*cda5da8dSAndroid Build Coastguard Worker
7*cda5da8dSAndroid Build Coastguard WorkerTo write out a plist file, use the dump(value, file)
8*cda5da8dSAndroid Build Coastguard Workerfunction. 'value' is the top level object, 'file' is
9*cda5da8dSAndroid Build Coastguard Workera (writable) file object.
10*cda5da8dSAndroid Build Coastguard Worker
11*cda5da8dSAndroid Build Coastguard WorkerTo parse a plist from a file, use the load(file) function,
12*cda5da8dSAndroid Build Coastguard Workerwith a (readable) file object as the only argument. It
13*cda5da8dSAndroid Build Coastguard Workerreturns the top level object (again, usually a dictionary).
14*cda5da8dSAndroid Build Coastguard Worker
15*cda5da8dSAndroid Build Coastguard WorkerTo work with plist data in bytes objects, you can use loads()
16*cda5da8dSAndroid Build Coastguard Workerand dumps().
17*cda5da8dSAndroid Build Coastguard Worker
18*cda5da8dSAndroid Build Coastguard WorkerValues can be strings, integers, floats, booleans, tuples, lists,
19*cda5da8dSAndroid Build Coastguard Workerdictionaries (but only with string keys), Data, bytes, bytearray, or
20*cda5da8dSAndroid Build Coastguard Workerdatetime.datetime objects.
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard WorkerGenerate Plist example:
23*cda5da8dSAndroid Build Coastguard Worker
24*cda5da8dSAndroid Build Coastguard Worker    import datetime
25*cda5da8dSAndroid Build Coastguard Worker    import plistlib
26*cda5da8dSAndroid Build Coastguard Worker
27*cda5da8dSAndroid Build Coastguard Worker    pl = dict(
28*cda5da8dSAndroid Build Coastguard Worker        aString = "Doodah",
29*cda5da8dSAndroid Build Coastguard Worker        aList = ["A", "B", 12, 32.1, [1, 2, 3]],
30*cda5da8dSAndroid Build Coastguard Worker        aFloat = 0.1,
31*cda5da8dSAndroid Build Coastguard Worker        anInt = 728,
32*cda5da8dSAndroid Build Coastguard Worker        aDict = dict(
33*cda5da8dSAndroid Build Coastguard Worker            anotherString = "<hello & hi there!>",
34*cda5da8dSAndroid Build Coastguard Worker            aThirdString = "M\xe4ssig, Ma\xdf",
35*cda5da8dSAndroid Build Coastguard Worker            aTrueValue = True,
36*cda5da8dSAndroid Build Coastguard Worker            aFalseValue = False,
37*cda5da8dSAndroid Build Coastguard Worker        ),
38*cda5da8dSAndroid Build Coastguard Worker        someData = b"<binary gunk>",
39*cda5da8dSAndroid Build Coastguard Worker        someMoreData = b"<lots of binary gunk>" * 10,
40*cda5da8dSAndroid Build Coastguard Worker        aDate = datetime.datetime.now()
41*cda5da8dSAndroid Build Coastguard Worker    )
42*cda5da8dSAndroid Build Coastguard Worker    print(plistlib.dumps(pl).decode())
43*cda5da8dSAndroid Build Coastguard Worker
44*cda5da8dSAndroid Build Coastguard WorkerParse Plist example:
45*cda5da8dSAndroid Build Coastguard Worker
46*cda5da8dSAndroid Build Coastguard Worker    import plistlib
47*cda5da8dSAndroid Build Coastguard Worker
48*cda5da8dSAndroid Build Coastguard Worker    plist = b'''<plist version="1.0">
49*cda5da8dSAndroid Build Coastguard Worker    <dict>
50*cda5da8dSAndroid Build Coastguard Worker        <key>foo</key>
51*cda5da8dSAndroid Build Coastguard Worker        <string>bar</string>
52*cda5da8dSAndroid Build Coastguard Worker    </dict>
53*cda5da8dSAndroid Build Coastguard Worker    </plist>'''
54*cda5da8dSAndroid Build Coastguard Worker    pl = plistlib.loads(plist)
55*cda5da8dSAndroid Build Coastguard Worker    print(pl["foo"])
56*cda5da8dSAndroid Build Coastguard Worker"""
57*cda5da8dSAndroid Build Coastguard Worker__all__ = [
58*cda5da8dSAndroid Build Coastguard Worker    "InvalidFileException", "FMT_XML", "FMT_BINARY", "load", "dump", "loads", "dumps", "UID"
59*cda5da8dSAndroid Build Coastguard Worker]
60*cda5da8dSAndroid Build Coastguard Worker
61*cda5da8dSAndroid Build Coastguard Workerimport binascii
62*cda5da8dSAndroid Build Coastguard Workerimport codecs
63*cda5da8dSAndroid Build Coastguard Workerimport datetime
64*cda5da8dSAndroid Build Coastguard Workerimport enum
65*cda5da8dSAndroid Build Coastguard Workerfrom io import BytesIO
66*cda5da8dSAndroid Build Coastguard Workerimport itertools
67*cda5da8dSAndroid Build Coastguard Workerimport os
68*cda5da8dSAndroid Build Coastguard Workerimport re
69*cda5da8dSAndroid Build Coastguard Workerimport struct
70*cda5da8dSAndroid Build Coastguard Workerfrom xml.parsers.expat import ParserCreate
71*cda5da8dSAndroid Build Coastguard Worker
72*cda5da8dSAndroid Build Coastguard Worker
73*cda5da8dSAndroid Build Coastguard WorkerPlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
74*cda5da8dSAndroid Build Coastguard Workerglobals().update(PlistFormat.__members__)
75*cda5da8dSAndroid Build Coastguard Worker
76*cda5da8dSAndroid Build Coastguard Worker
77*cda5da8dSAndroid Build Coastguard Workerclass UID:
78*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, data):
79*cda5da8dSAndroid Build Coastguard Worker        if not isinstance(data, int):
80*cda5da8dSAndroid Build Coastguard Worker            raise TypeError("data must be an int")
81*cda5da8dSAndroid Build Coastguard Worker        if data >= 1 << 64:
82*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("UIDs cannot be >= 2**64")
83*cda5da8dSAndroid Build Coastguard Worker        if data < 0:
84*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("UIDs must be positive")
85*cda5da8dSAndroid Build Coastguard Worker        self.data = data
86*cda5da8dSAndroid Build Coastguard Worker
87*cda5da8dSAndroid Build Coastguard Worker    def __index__(self):
88*cda5da8dSAndroid Build Coastguard Worker        return self.data
89*cda5da8dSAndroid Build Coastguard Worker
90*cda5da8dSAndroid Build Coastguard Worker    def __repr__(self):
91*cda5da8dSAndroid Build Coastguard Worker        return "%s(%s)" % (self.__class__.__name__, repr(self.data))
92*cda5da8dSAndroid Build Coastguard Worker
93*cda5da8dSAndroid Build Coastguard Worker    def __reduce__(self):
94*cda5da8dSAndroid Build Coastguard Worker        return self.__class__, (self.data,)
95*cda5da8dSAndroid Build Coastguard Worker
96*cda5da8dSAndroid Build Coastguard Worker    def __eq__(self, other):
97*cda5da8dSAndroid Build Coastguard Worker        if not isinstance(other, UID):
98*cda5da8dSAndroid Build Coastguard Worker            return NotImplemented
99*cda5da8dSAndroid Build Coastguard Worker        return self.data == other.data
100*cda5da8dSAndroid Build Coastguard Worker
101*cda5da8dSAndroid Build Coastguard Worker    def __hash__(self):
102*cda5da8dSAndroid Build Coastguard Worker        return hash(self.data)
103*cda5da8dSAndroid Build Coastguard Worker
104*cda5da8dSAndroid Build Coastguard Worker#
105*cda5da8dSAndroid Build Coastguard Worker# XML support
106*cda5da8dSAndroid Build Coastguard Worker#
107*cda5da8dSAndroid Build Coastguard Worker
108*cda5da8dSAndroid Build Coastguard Worker
109*cda5da8dSAndroid Build Coastguard Worker# XML 'header'
110*cda5da8dSAndroid Build Coastguard WorkerPLISTHEADER = b"""\
111*cda5da8dSAndroid Build Coastguard Worker<?xml version="1.0" encoding="UTF-8"?>
112*cda5da8dSAndroid Build Coastguard Worker<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
113*cda5da8dSAndroid Build Coastguard Worker"""
114*cda5da8dSAndroid Build Coastguard Worker
115*cda5da8dSAndroid Build Coastguard Worker
116*cda5da8dSAndroid Build Coastguard Worker# Regex to find any control chars, except for \t \n and \r
117*cda5da8dSAndroid Build Coastguard Worker_controlCharPat = re.compile(
118*cda5da8dSAndroid Build Coastguard Worker    r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"
119*cda5da8dSAndroid Build Coastguard Worker    r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
120*cda5da8dSAndroid Build Coastguard Worker
121*cda5da8dSAndroid Build Coastguard Workerdef _encode_base64(s, maxlinelength=76):
122*cda5da8dSAndroid Build Coastguard Worker    # copied from base64.encodebytes(), with added maxlinelength argument
123*cda5da8dSAndroid Build Coastguard Worker    maxbinsize = (maxlinelength//4)*3
124*cda5da8dSAndroid Build Coastguard Worker    pieces = []
125*cda5da8dSAndroid Build Coastguard Worker    for i in range(0, len(s), maxbinsize):
126*cda5da8dSAndroid Build Coastguard Worker        chunk = s[i : i + maxbinsize]
127*cda5da8dSAndroid Build Coastguard Worker        pieces.append(binascii.b2a_base64(chunk))
128*cda5da8dSAndroid Build Coastguard Worker    return b''.join(pieces)
129*cda5da8dSAndroid Build Coastguard Worker
130*cda5da8dSAndroid Build Coastguard Workerdef _decode_base64(s):
131*cda5da8dSAndroid Build Coastguard Worker    if isinstance(s, str):
132*cda5da8dSAndroid Build Coastguard Worker        return binascii.a2b_base64(s.encode("utf-8"))
133*cda5da8dSAndroid Build Coastguard Worker
134*cda5da8dSAndroid Build Coastguard Worker    else:
135*cda5da8dSAndroid Build Coastguard Worker        return binascii.a2b_base64(s)
136*cda5da8dSAndroid Build Coastguard Worker
137*cda5da8dSAndroid Build Coastguard Worker# Contents should conform to a subset of ISO 8601
138*cda5da8dSAndroid Build Coastguard Worker# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'.  Smaller units
139*cda5da8dSAndroid Build Coastguard Worker# may be omitted with #  a loss of precision)
140*cda5da8dSAndroid Build Coastguard Worker_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z", re.ASCII)
141*cda5da8dSAndroid Build Coastguard Worker
142*cda5da8dSAndroid Build Coastguard Worker
143*cda5da8dSAndroid Build Coastguard Workerdef _date_from_string(s):
144*cda5da8dSAndroid Build Coastguard Worker    order = ('year', 'month', 'day', 'hour', 'minute', 'second')
145*cda5da8dSAndroid Build Coastguard Worker    gd = _dateParser.match(s).groupdict()
146*cda5da8dSAndroid Build Coastguard Worker    lst = []
147*cda5da8dSAndroid Build Coastguard Worker    for key in order:
148*cda5da8dSAndroid Build Coastguard Worker        val = gd[key]
149*cda5da8dSAndroid Build Coastguard Worker        if val is None:
150*cda5da8dSAndroid Build Coastguard Worker            break
151*cda5da8dSAndroid Build Coastguard Worker        lst.append(int(val))
152*cda5da8dSAndroid Build Coastguard Worker    return datetime.datetime(*lst)
153*cda5da8dSAndroid Build Coastguard Worker
154*cda5da8dSAndroid Build Coastguard Worker
155*cda5da8dSAndroid Build Coastguard Workerdef _date_to_string(d):
156*cda5da8dSAndroid Build Coastguard Worker    return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (
157*cda5da8dSAndroid Build Coastguard Worker        d.year, d.month, d.day,
158*cda5da8dSAndroid Build Coastguard Worker        d.hour, d.minute, d.second
159*cda5da8dSAndroid Build Coastguard Worker    )
160*cda5da8dSAndroid Build Coastguard Worker
161*cda5da8dSAndroid Build Coastguard Workerdef _escape(text):
162*cda5da8dSAndroid Build Coastguard Worker    m = _controlCharPat.search(text)
163*cda5da8dSAndroid Build Coastguard Worker    if m is not None:
164*cda5da8dSAndroid Build Coastguard Worker        raise ValueError("strings can't contain control characters; "
165*cda5da8dSAndroid Build Coastguard Worker                         "use bytes instead")
166*cda5da8dSAndroid Build Coastguard Worker    text = text.replace("\r\n", "\n")       # convert DOS line endings
167*cda5da8dSAndroid Build Coastguard Worker    text = text.replace("\r", "\n")         # convert Mac line endings
168*cda5da8dSAndroid Build Coastguard Worker    text = text.replace("&", "&amp;")       # escape '&'
169*cda5da8dSAndroid Build Coastguard Worker    text = text.replace("<", "&lt;")        # escape '<'
170*cda5da8dSAndroid Build Coastguard Worker    text = text.replace(">", "&gt;")        # escape '>'
171*cda5da8dSAndroid Build Coastguard Worker    return text
172*cda5da8dSAndroid Build Coastguard Worker
173*cda5da8dSAndroid Build Coastguard Workerclass _PlistParser:
174*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, dict_type):
175*cda5da8dSAndroid Build Coastguard Worker        self.stack = []
176*cda5da8dSAndroid Build Coastguard Worker        self.current_key = None
177*cda5da8dSAndroid Build Coastguard Worker        self.root = None
178*cda5da8dSAndroid Build Coastguard Worker        self._dict_type = dict_type
179*cda5da8dSAndroid Build Coastguard Worker
180*cda5da8dSAndroid Build Coastguard Worker    def parse(self, fileobj):
181*cda5da8dSAndroid Build Coastguard Worker        self.parser = ParserCreate()
182*cda5da8dSAndroid Build Coastguard Worker        self.parser.StartElementHandler = self.handle_begin_element
183*cda5da8dSAndroid Build Coastguard Worker        self.parser.EndElementHandler = self.handle_end_element
184*cda5da8dSAndroid Build Coastguard Worker        self.parser.CharacterDataHandler = self.handle_data
185*cda5da8dSAndroid Build Coastguard Worker        self.parser.EntityDeclHandler = self.handle_entity_decl
186*cda5da8dSAndroid Build Coastguard Worker        self.parser.ParseFile(fileobj)
187*cda5da8dSAndroid Build Coastguard Worker        return self.root
188*cda5da8dSAndroid Build Coastguard Worker
189*cda5da8dSAndroid Build Coastguard Worker    def handle_entity_decl(self, entity_name, is_parameter_entity, value, base, system_id, public_id, notation_name):
190*cda5da8dSAndroid Build Coastguard Worker        # Reject plist files with entity declarations to avoid XML vulnerabilities in expat.
191*cda5da8dSAndroid Build Coastguard Worker        # Regular plist files don't contain those declarations, and Apple's plutil tool does not
192*cda5da8dSAndroid Build Coastguard Worker        # accept them either.
193*cda5da8dSAndroid Build Coastguard Worker        raise InvalidFileException("XML entity declarations are not supported in plist files")
194*cda5da8dSAndroid Build Coastguard Worker
195*cda5da8dSAndroid Build Coastguard Worker    def handle_begin_element(self, element, attrs):
196*cda5da8dSAndroid Build Coastguard Worker        self.data = []
197*cda5da8dSAndroid Build Coastguard Worker        handler = getattr(self, "begin_" + element, None)
198*cda5da8dSAndroid Build Coastguard Worker        if handler is not None:
199*cda5da8dSAndroid Build Coastguard Worker            handler(attrs)
200*cda5da8dSAndroid Build Coastguard Worker
201*cda5da8dSAndroid Build Coastguard Worker    def handle_end_element(self, element):
202*cda5da8dSAndroid Build Coastguard Worker        handler = getattr(self, "end_" + element, None)
203*cda5da8dSAndroid Build Coastguard Worker        if handler is not None:
204*cda5da8dSAndroid Build Coastguard Worker            handler()
205*cda5da8dSAndroid Build Coastguard Worker
206*cda5da8dSAndroid Build Coastguard Worker    def handle_data(self, data):
207*cda5da8dSAndroid Build Coastguard Worker        self.data.append(data)
208*cda5da8dSAndroid Build Coastguard Worker
209*cda5da8dSAndroid Build Coastguard Worker    def add_object(self, value):
210*cda5da8dSAndroid Build Coastguard Worker        if self.current_key is not None:
211*cda5da8dSAndroid Build Coastguard Worker            if not isinstance(self.stack[-1], type({})):
212*cda5da8dSAndroid Build Coastguard Worker                raise ValueError("unexpected element at line %d" %
213*cda5da8dSAndroid Build Coastguard Worker                                 self.parser.CurrentLineNumber)
214*cda5da8dSAndroid Build Coastguard Worker            self.stack[-1][self.current_key] = value
215*cda5da8dSAndroid Build Coastguard Worker            self.current_key = None
216*cda5da8dSAndroid Build Coastguard Worker        elif not self.stack:
217*cda5da8dSAndroid Build Coastguard Worker            # this is the root object
218*cda5da8dSAndroid Build Coastguard Worker            self.root = value
219*cda5da8dSAndroid Build Coastguard Worker        else:
220*cda5da8dSAndroid Build Coastguard Worker            if not isinstance(self.stack[-1], type([])):
221*cda5da8dSAndroid Build Coastguard Worker                raise ValueError("unexpected element at line %d" %
222*cda5da8dSAndroid Build Coastguard Worker                                 self.parser.CurrentLineNumber)
223*cda5da8dSAndroid Build Coastguard Worker            self.stack[-1].append(value)
224*cda5da8dSAndroid Build Coastguard Worker
225*cda5da8dSAndroid Build Coastguard Worker    def get_data(self):
226*cda5da8dSAndroid Build Coastguard Worker        data = ''.join(self.data)
227*cda5da8dSAndroid Build Coastguard Worker        self.data = []
228*cda5da8dSAndroid Build Coastguard Worker        return data
229*cda5da8dSAndroid Build Coastguard Worker
230*cda5da8dSAndroid Build Coastguard Worker    # element handlers
231*cda5da8dSAndroid Build Coastguard Worker
232*cda5da8dSAndroid Build Coastguard Worker    def begin_dict(self, attrs):
233*cda5da8dSAndroid Build Coastguard Worker        d = self._dict_type()
234*cda5da8dSAndroid Build Coastguard Worker        self.add_object(d)
235*cda5da8dSAndroid Build Coastguard Worker        self.stack.append(d)
236*cda5da8dSAndroid Build Coastguard Worker
237*cda5da8dSAndroid Build Coastguard Worker    def end_dict(self):
238*cda5da8dSAndroid Build Coastguard Worker        if self.current_key:
239*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("missing value for key '%s' at line %d" %
240*cda5da8dSAndroid Build Coastguard Worker                             (self.current_key,self.parser.CurrentLineNumber))
241*cda5da8dSAndroid Build Coastguard Worker        self.stack.pop()
242*cda5da8dSAndroid Build Coastguard Worker
243*cda5da8dSAndroid Build Coastguard Worker    def end_key(self):
244*cda5da8dSAndroid Build Coastguard Worker        if self.current_key or not isinstance(self.stack[-1], type({})):
245*cda5da8dSAndroid Build Coastguard Worker            raise ValueError("unexpected key at line %d" %
246*cda5da8dSAndroid Build Coastguard Worker                             self.parser.CurrentLineNumber)
247*cda5da8dSAndroid Build Coastguard Worker        self.current_key = self.get_data()
248*cda5da8dSAndroid Build Coastguard Worker
249*cda5da8dSAndroid Build Coastguard Worker    def begin_array(self, attrs):
250*cda5da8dSAndroid Build Coastguard Worker        a = []
251*cda5da8dSAndroid Build Coastguard Worker        self.add_object(a)
252*cda5da8dSAndroid Build Coastguard Worker        self.stack.append(a)
253*cda5da8dSAndroid Build Coastguard Worker
254*cda5da8dSAndroid Build Coastguard Worker    def end_array(self):
255*cda5da8dSAndroid Build Coastguard Worker        self.stack.pop()
256*cda5da8dSAndroid Build Coastguard Worker
257*cda5da8dSAndroid Build Coastguard Worker    def end_true(self):
258*cda5da8dSAndroid Build Coastguard Worker        self.add_object(True)
259*cda5da8dSAndroid Build Coastguard Worker
260*cda5da8dSAndroid Build Coastguard Worker    def end_false(self):
261*cda5da8dSAndroid Build Coastguard Worker        self.add_object(False)
262*cda5da8dSAndroid Build Coastguard Worker
263*cda5da8dSAndroid Build Coastguard Worker    def end_integer(self):
264*cda5da8dSAndroid Build Coastguard Worker        raw = self.get_data()
265*cda5da8dSAndroid Build Coastguard Worker        if raw.startswith('0x') or raw.startswith('0X'):
266*cda5da8dSAndroid Build Coastguard Worker            self.add_object(int(raw, 16))
267*cda5da8dSAndroid Build Coastguard Worker        else:
268*cda5da8dSAndroid Build Coastguard Worker            self.add_object(int(raw))
269*cda5da8dSAndroid Build Coastguard Worker
270*cda5da8dSAndroid Build Coastguard Worker    def end_real(self):
271*cda5da8dSAndroid Build Coastguard Worker        self.add_object(float(self.get_data()))
272*cda5da8dSAndroid Build Coastguard Worker
273*cda5da8dSAndroid Build Coastguard Worker    def end_string(self):
274*cda5da8dSAndroid Build Coastguard Worker        self.add_object(self.get_data())
275*cda5da8dSAndroid Build Coastguard Worker
276*cda5da8dSAndroid Build Coastguard Worker    def end_data(self):
277*cda5da8dSAndroid Build Coastguard Worker        self.add_object(_decode_base64(self.get_data()))
278*cda5da8dSAndroid Build Coastguard Worker
279*cda5da8dSAndroid Build Coastguard Worker    def end_date(self):
280*cda5da8dSAndroid Build Coastguard Worker        self.add_object(_date_from_string(self.get_data()))
281*cda5da8dSAndroid Build Coastguard Worker
282*cda5da8dSAndroid Build Coastguard Worker
283*cda5da8dSAndroid Build Coastguard Workerclass _DumbXMLWriter:
284*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, file, indent_level=0, indent="\t"):
285*cda5da8dSAndroid Build Coastguard Worker        self.file = file
286*cda5da8dSAndroid Build Coastguard Worker        self.stack = []
287*cda5da8dSAndroid Build Coastguard Worker        self._indent_level = indent_level
288*cda5da8dSAndroid Build Coastguard Worker        self.indent = indent
289*cda5da8dSAndroid Build Coastguard Worker
290*cda5da8dSAndroid Build Coastguard Worker    def begin_element(self, element):
291*cda5da8dSAndroid Build Coastguard Worker        self.stack.append(element)
292*cda5da8dSAndroid Build Coastguard Worker        self.writeln("<%s>" % element)
293*cda5da8dSAndroid Build Coastguard Worker        self._indent_level += 1
294*cda5da8dSAndroid Build Coastguard Worker
295*cda5da8dSAndroid Build Coastguard Worker    def end_element(self, element):
296*cda5da8dSAndroid Build Coastguard Worker        assert self._indent_level > 0
297*cda5da8dSAndroid Build Coastguard Worker        assert self.stack.pop() == element
298*cda5da8dSAndroid Build Coastguard Worker        self._indent_level -= 1
299*cda5da8dSAndroid Build Coastguard Worker        self.writeln("</%s>" % element)
300*cda5da8dSAndroid Build Coastguard Worker
301*cda5da8dSAndroid Build Coastguard Worker    def simple_element(self, element, value=None):
302*cda5da8dSAndroid Build Coastguard Worker        if value is not None:
303*cda5da8dSAndroid Build Coastguard Worker            value = _escape(value)
304*cda5da8dSAndroid Build Coastguard Worker            self.writeln("<%s>%s</%s>" % (element, value, element))
305*cda5da8dSAndroid Build Coastguard Worker
306*cda5da8dSAndroid Build Coastguard Worker        else:
307*cda5da8dSAndroid Build Coastguard Worker            self.writeln("<%s/>" % element)
308*cda5da8dSAndroid Build Coastguard Worker
309*cda5da8dSAndroid Build Coastguard Worker    def writeln(self, line):
310*cda5da8dSAndroid Build Coastguard Worker        if line:
311*cda5da8dSAndroid Build Coastguard Worker            # plist has fixed encoding of utf-8
312*cda5da8dSAndroid Build Coastguard Worker
313*cda5da8dSAndroid Build Coastguard Worker            # XXX: is this test needed?
314*cda5da8dSAndroid Build Coastguard Worker            if isinstance(line, str):
315*cda5da8dSAndroid Build Coastguard Worker                line = line.encode('utf-8')
316*cda5da8dSAndroid Build Coastguard Worker            self.file.write(self._indent_level * self.indent)
317*cda5da8dSAndroid Build Coastguard Worker            self.file.write(line)
318*cda5da8dSAndroid Build Coastguard Worker        self.file.write(b'\n')
319*cda5da8dSAndroid Build Coastguard Worker
320*cda5da8dSAndroid Build Coastguard Worker
321*cda5da8dSAndroid Build Coastguard Workerclass _PlistWriter(_DumbXMLWriter):
322*cda5da8dSAndroid Build Coastguard Worker    def __init__(
323*cda5da8dSAndroid Build Coastguard Worker            self, file, indent_level=0, indent=b"\t", writeHeader=1,
324*cda5da8dSAndroid Build Coastguard Worker            sort_keys=True, skipkeys=False):
325*cda5da8dSAndroid Build Coastguard Worker
326*cda5da8dSAndroid Build Coastguard Worker        if writeHeader:
327*cda5da8dSAndroid Build Coastguard Worker            file.write(PLISTHEADER)
328*cda5da8dSAndroid Build Coastguard Worker        _DumbXMLWriter.__init__(self, file, indent_level, indent)
329*cda5da8dSAndroid Build Coastguard Worker        self._sort_keys = sort_keys
330*cda5da8dSAndroid Build Coastguard Worker        self._skipkeys = skipkeys
331*cda5da8dSAndroid Build Coastguard Worker
332*cda5da8dSAndroid Build Coastguard Worker    def write(self, value):
333*cda5da8dSAndroid Build Coastguard Worker        self.writeln("<plist version=\"1.0\">")
334*cda5da8dSAndroid Build Coastguard Worker        self.write_value(value)
335*cda5da8dSAndroid Build Coastguard Worker        self.writeln("</plist>")
336*cda5da8dSAndroid Build Coastguard Worker
337*cda5da8dSAndroid Build Coastguard Worker    def write_value(self, value):
338*cda5da8dSAndroid Build Coastguard Worker        if isinstance(value, str):
339*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("string", value)
340*cda5da8dSAndroid Build Coastguard Worker
341*cda5da8dSAndroid Build Coastguard Worker        elif value is True:
342*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("true")
343*cda5da8dSAndroid Build Coastguard Worker
344*cda5da8dSAndroid Build Coastguard Worker        elif value is False:
345*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("false")
346*cda5da8dSAndroid Build Coastguard Worker
347*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, int):
348*cda5da8dSAndroid Build Coastguard Worker            if -1 << 63 <= value < 1 << 64:
349*cda5da8dSAndroid Build Coastguard Worker                self.simple_element("integer", "%d" % value)
350*cda5da8dSAndroid Build Coastguard Worker            else:
351*cda5da8dSAndroid Build Coastguard Worker                raise OverflowError(value)
352*cda5da8dSAndroid Build Coastguard Worker
353*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, float):
354*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("real", repr(value))
355*cda5da8dSAndroid Build Coastguard Worker
356*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, dict):
357*cda5da8dSAndroid Build Coastguard Worker            self.write_dict(value)
358*cda5da8dSAndroid Build Coastguard Worker
359*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, (bytes, bytearray)):
360*cda5da8dSAndroid Build Coastguard Worker            self.write_bytes(value)
361*cda5da8dSAndroid Build Coastguard Worker
362*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, datetime.datetime):
363*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("date", _date_to_string(value))
364*cda5da8dSAndroid Build Coastguard Worker
365*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, (tuple, list)):
366*cda5da8dSAndroid Build Coastguard Worker            self.write_array(value)
367*cda5da8dSAndroid Build Coastguard Worker
368*cda5da8dSAndroid Build Coastguard Worker        else:
369*cda5da8dSAndroid Build Coastguard Worker            raise TypeError("unsupported type: %s" % type(value))
370*cda5da8dSAndroid Build Coastguard Worker
371*cda5da8dSAndroid Build Coastguard Worker    def write_bytes(self, data):
372*cda5da8dSAndroid Build Coastguard Worker        self.begin_element("data")
373*cda5da8dSAndroid Build Coastguard Worker        self._indent_level -= 1
374*cda5da8dSAndroid Build Coastguard Worker        maxlinelength = max(
375*cda5da8dSAndroid Build Coastguard Worker            16,
376*cda5da8dSAndroid Build Coastguard Worker            76 - len(self.indent.replace(b"\t", b" " * 8) * self._indent_level))
377*cda5da8dSAndroid Build Coastguard Worker
378*cda5da8dSAndroid Build Coastguard Worker        for line in _encode_base64(data, maxlinelength).split(b"\n"):
379*cda5da8dSAndroid Build Coastguard Worker            if line:
380*cda5da8dSAndroid Build Coastguard Worker                self.writeln(line)
381*cda5da8dSAndroid Build Coastguard Worker        self._indent_level += 1
382*cda5da8dSAndroid Build Coastguard Worker        self.end_element("data")
383*cda5da8dSAndroid Build Coastguard Worker
384*cda5da8dSAndroid Build Coastguard Worker    def write_dict(self, d):
385*cda5da8dSAndroid Build Coastguard Worker        if d:
386*cda5da8dSAndroid Build Coastguard Worker            self.begin_element("dict")
387*cda5da8dSAndroid Build Coastguard Worker            if self._sort_keys:
388*cda5da8dSAndroid Build Coastguard Worker                items = sorted(d.items())
389*cda5da8dSAndroid Build Coastguard Worker            else:
390*cda5da8dSAndroid Build Coastguard Worker                items = d.items()
391*cda5da8dSAndroid Build Coastguard Worker
392*cda5da8dSAndroid Build Coastguard Worker            for key, value in items:
393*cda5da8dSAndroid Build Coastguard Worker                if not isinstance(key, str):
394*cda5da8dSAndroid Build Coastguard Worker                    if self._skipkeys:
395*cda5da8dSAndroid Build Coastguard Worker                        continue
396*cda5da8dSAndroid Build Coastguard Worker                    raise TypeError("keys must be strings")
397*cda5da8dSAndroid Build Coastguard Worker                self.simple_element("key", key)
398*cda5da8dSAndroid Build Coastguard Worker                self.write_value(value)
399*cda5da8dSAndroid Build Coastguard Worker            self.end_element("dict")
400*cda5da8dSAndroid Build Coastguard Worker
401*cda5da8dSAndroid Build Coastguard Worker        else:
402*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("dict")
403*cda5da8dSAndroid Build Coastguard Worker
404*cda5da8dSAndroid Build Coastguard Worker    def write_array(self, array):
405*cda5da8dSAndroid Build Coastguard Worker        if array:
406*cda5da8dSAndroid Build Coastguard Worker            self.begin_element("array")
407*cda5da8dSAndroid Build Coastguard Worker            for value in array:
408*cda5da8dSAndroid Build Coastguard Worker                self.write_value(value)
409*cda5da8dSAndroid Build Coastguard Worker            self.end_element("array")
410*cda5da8dSAndroid Build Coastguard Worker
411*cda5da8dSAndroid Build Coastguard Worker        else:
412*cda5da8dSAndroid Build Coastguard Worker            self.simple_element("array")
413*cda5da8dSAndroid Build Coastguard Worker
414*cda5da8dSAndroid Build Coastguard Worker
415*cda5da8dSAndroid Build Coastguard Workerdef _is_fmt_xml(header):
416*cda5da8dSAndroid Build Coastguard Worker    prefixes = (b'<?xml', b'<plist')
417*cda5da8dSAndroid Build Coastguard Worker
418*cda5da8dSAndroid Build Coastguard Worker    for pfx in prefixes:
419*cda5da8dSAndroid Build Coastguard Worker        if header.startswith(pfx):
420*cda5da8dSAndroid Build Coastguard Worker            return True
421*cda5da8dSAndroid Build Coastguard Worker
422*cda5da8dSAndroid Build Coastguard Worker    # Also check for alternative XML encodings, this is slightly
423*cda5da8dSAndroid Build Coastguard Worker    # overkill because the Apple tools (and plistlib) will not
424*cda5da8dSAndroid Build Coastguard Worker    # generate files with these encodings.
425*cda5da8dSAndroid Build Coastguard Worker    for bom, encoding in (
426*cda5da8dSAndroid Build Coastguard Worker                (codecs.BOM_UTF8, "utf-8"),
427*cda5da8dSAndroid Build Coastguard Worker                (codecs.BOM_UTF16_BE, "utf-16-be"),
428*cda5da8dSAndroid Build Coastguard Worker                (codecs.BOM_UTF16_LE, "utf-16-le"),
429*cda5da8dSAndroid Build Coastguard Worker                # expat does not support utf-32
430*cda5da8dSAndroid Build Coastguard Worker                #(codecs.BOM_UTF32_BE, "utf-32-be"),
431*cda5da8dSAndroid Build Coastguard Worker                #(codecs.BOM_UTF32_LE, "utf-32-le"),
432*cda5da8dSAndroid Build Coastguard Worker            ):
433*cda5da8dSAndroid Build Coastguard Worker        if not header.startswith(bom):
434*cda5da8dSAndroid Build Coastguard Worker            continue
435*cda5da8dSAndroid Build Coastguard Worker
436*cda5da8dSAndroid Build Coastguard Worker        for start in prefixes:
437*cda5da8dSAndroid Build Coastguard Worker            prefix = bom + start.decode('ascii').encode(encoding)
438*cda5da8dSAndroid Build Coastguard Worker            if header[:len(prefix)] == prefix:
439*cda5da8dSAndroid Build Coastguard Worker                return True
440*cda5da8dSAndroid Build Coastguard Worker
441*cda5da8dSAndroid Build Coastguard Worker    return False
442*cda5da8dSAndroid Build Coastguard Worker
443*cda5da8dSAndroid Build Coastguard Worker#
444*cda5da8dSAndroid Build Coastguard Worker# Binary Plist
445*cda5da8dSAndroid Build Coastguard Worker#
446*cda5da8dSAndroid Build Coastguard Worker
447*cda5da8dSAndroid Build Coastguard Worker
448*cda5da8dSAndroid Build Coastguard Workerclass InvalidFileException (ValueError):
449*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, message="Invalid file"):
450*cda5da8dSAndroid Build Coastguard Worker        ValueError.__init__(self, message)
451*cda5da8dSAndroid Build Coastguard Worker
452*cda5da8dSAndroid Build Coastguard Worker_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}
453*cda5da8dSAndroid Build Coastguard Worker
454*cda5da8dSAndroid Build Coastguard Worker_undefined = object()
455*cda5da8dSAndroid Build Coastguard Worker
456*cda5da8dSAndroid Build Coastguard Workerclass _BinaryPlistParser:
457*cda5da8dSAndroid Build Coastguard Worker    """
458*cda5da8dSAndroid Build Coastguard Worker    Read or write a binary plist file, following the description of the binary
459*cda5da8dSAndroid Build Coastguard Worker    format.  Raise InvalidFileException in case of error, otherwise return the
460*cda5da8dSAndroid Build Coastguard Worker    root object.
461*cda5da8dSAndroid Build Coastguard Worker
462*cda5da8dSAndroid Build Coastguard Worker    see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
463*cda5da8dSAndroid Build Coastguard Worker    """
464*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, dict_type):
465*cda5da8dSAndroid Build Coastguard Worker        self._dict_type = dict_type
466*cda5da8dSAndroid Build Coastguard Worker
467*cda5da8dSAndroid Build Coastguard Worker    def parse(self, fp):
468*cda5da8dSAndroid Build Coastguard Worker        try:
469*cda5da8dSAndroid Build Coastguard Worker            # The basic file format:
470*cda5da8dSAndroid Build Coastguard Worker            # HEADER
471*cda5da8dSAndroid Build Coastguard Worker            # object...
472*cda5da8dSAndroid Build Coastguard Worker            # refid->offset...
473*cda5da8dSAndroid Build Coastguard Worker            # TRAILER
474*cda5da8dSAndroid Build Coastguard Worker            self._fp = fp
475*cda5da8dSAndroid Build Coastguard Worker            self._fp.seek(-32, os.SEEK_END)
476*cda5da8dSAndroid Build Coastguard Worker            trailer = self._fp.read(32)
477*cda5da8dSAndroid Build Coastguard Worker            if len(trailer) != 32:
478*cda5da8dSAndroid Build Coastguard Worker                raise InvalidFileException()
479*cda5da8dSAndroid Build Coastguard Worker            (
480*cda5da8dSAndroid Build Coastguard Worker                offset_size, self._ref_size, num_objects, top_object,
481*cda5da8dSAndroid Build Coastguard Worker                offset_table_offset
482*cda5da8dSAndroid Build Coastguard Worker            ) = struct.unpack('>6xBBQQQ', trailer)
483*cda5da8dSAndroid Build Coastguard Worker            self._fp.seek(offset_table_offset)
484*cda5da8dSAndroid Build Coastguard Worker            self._object_offsets = self._read_ints(num_objects, offset_size)
485*cda5da8dSAndroid Build Coastguard Worker            self._objects = [_undefined] * num_objects
486*cda5da8dSAndroid Build Coastguard Worker            return self._read_object(top_object)
487*cda5da8dSAndroid Build Coastguard Worker
488*cda5da8dSAndroid Build Coastguard Worker        except (OSError, IndexError, struct.error, OverflowError,
489*cda5da8dSAndroid Build Coastguard Worker                ValueError):
490*cda5da8dSAndroid Build Coastguard Worker            raise InvalidFileException()
491*cda5da8dSAndroid Build Coastguard Worker
492*cda5da8dSAndroid Build Coastguard Worker    def _get_size(self, tokenL):
493*cda5da8dSAndroid Build Coastguard Worker        """ return the size of the next object."""
494*cda5da8dSAndroid Build Coastguard Worker        if tokenL == 0xF:
495*cda5da8dSAndroid Build Coastguard Worker            m = self._fp.read(1)[0] & 0x3
496*cda5da8dSAndroid Build Coastguard Worker            s = 1 << m
497*cda5da8dSAndroid Build Coastguard Worker            f = '>' + _BINARY_FORMAT[s]
498*cda5da8dSAndroid Build Coastguard Worker            return struct.unpack(f, self._fp.read(s))[0]
499*cda5da8dSAndroid Build Coastguard Worker
500*cda5da8dSAndroid Build Coastguard Worker        return tokenL
501*cda5da8dSAndroid Build Coastguard Worker
502*cda5da8dSAndroid Build Coastguard Worker    def _read_ints(self, n, size):
503*cda5da8dSAndroid Build Coastguard Worker        data = self._fp.read(size * n)
504*cda5da8dSAndroid Build Coastguard Worker        if size in _BINARY_FORMAT:
505*cda5da8dSAndroid Build Coastguard Worker            return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data)
506*cda5da8dSAndroid Build Coastguard Worker        else:
507*cda5da8dSAndroid Build Coastguard Worker            if not size or len(data) != size * n:
508*cda5da8dSAndroid Build Coastguard Worker                raise InvalidFileException()
509*cda5da8dSAndroid Build Coastguard Worker            return tuple(int.from_bytes(data[i: i + size], 'big')
510*cda5da8dSAndroid Build Coastguard Worker                         for i in range(0, size * n, size))
511*cda5da8dSAndroid Build Coastguard Worker
512*cda5da8dSAndroid Build Coastguard Worker    def _read_refs(self, n):
513*cda5da8dSAndroid Build Coastguard Worker        return self._read_ints(n, self._ref_size)
514*cda5da8dSAndroid Build Coastguard Worker
515*cda5da8dSAndroid Build Coastguard Worker    def _read_object(self, ref):
516*cda5da8dSAndroid Build Coastguard Worker        """
517*cda5da8dSAndroid Build Coastguard Worker        read the object by reference.
518*cda5da8dSAndroid Build Coastguard Worker
519*cda5da8dSAndroid Build Coastguard Worker        May recursively read sub-objects (content of an array/dict/set)
520*cda5da8dSAndroid Build Coastguard Worker        """
521*cda5da8dSAndroid Build Coastguard Worker        result = self._objects[ref]
522*cda5da8dSAndroid Build Coastguard Worker        if result is not _undefined:
523*cda5da8dSAndroid Build Coastguard Worker            return result
524*cda5da8dSAndroid Build Coastguard Worker
525*cda5da8dSAndroid Build Coastguard Worker        offset = self._object_offsets[ref]
526*cda5da8dSAndroid Build Coastguard Worker        self._fp.seek(offset)
527*cda5da8dSAndroid Build Coastguard Worker        token = self._fp.read(1)[0]
528*cda5da8dSAndroid Build Coastguard Worker        tokenH, tokenL = token & 0xF0, token & 0x0F
529*cda5da8dSAndroid Build Coastguard Worker
530*cda5da8dSAndroid Build Coastguard Worker        if token == 0x00:
531*cda5da8dSAndroid Build Coastguard Worker            result = None
532*cda5da8dSAndroid Build Coastguard Worker
533*cda5da8dSAndroid Build Coastguard Worker        elif token == 0x08:
534*cda5da8dSAndroid Build Coastguard Worker            result = False
535*cda5da8dSAndroid Build Coastguard Worker
536*cda5da8dSAndroid Build Coastguard Worker        elif token == 0x09:
537*cda5da8dSAndroid Build Coastguard Worker            result = True
538*cda5da8dSAndroid Build Coastguard Worker
539*cda5da8dSAndroid Build Coastguard Worker        # The referenced source code also mentions URL (0x0c, 0x0d) and
540*cda5da8dSAndroid Build Coastguard Worker        # UUID (0x0e), but neither can be generated using the Cocoa libraries.
541*cda5da8dSAndroid Build Coastguard Worker
542*cda5da8dSAndroid Build Coastguard Worker        elif token == 0x0f:
543*cda5da8dSAndroid Build Coastguard Worker            result = b''
544*cda5da8dSAndroid Build Coastguard Worker
545*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0x10:  # int
546*cda5da8dSAndroid Build Coastguard Worker            result = int.from_bytes(self._fp.read(1 << tokenL),
547*cda5da8dSAndroid Build Coastguard Worker                                    'big', signed=tokenL >= 3)
548*cda5da8dSAndroid Build Coastguard Worker
549*cda5da8dSAndroid Build Coastguard Worker        elif token == 0x22: # real
550*cda5da8dSAndroid Build Coastguard Worker            result = struct.unpack('>f', self._fp.read(4))[0]
551*cda5da8dSAndroid Build Coastguard Worker
552*cda5da8dSAndroid Build Coastguard Worker        elif token == 0x23: # real
553*cda5da8dSAndroid Build Coastguard Worker            result = struct.unpack('>d', self._fp.read(8))[0]
554*cda5da8dSAndroid Build Coastguard Worker
555*cda5da8dSAndroid Build Coastguard Worker        elif token == 0x33:  # date
556*cda5da8dSAndroid Build Coastguard Worker            f = struct.unpack('>d', self._fp.read(8))[0]
557*cda5da8dSAndroid Build Coastguard Worker            # timestamp 0 of binary plists corresponds to 1/1/2001
558*cda5da8dSAndroid Build Coastguard Worker            # (year of Mac OS X 10.0), instead of 1/1/1970.
559*cda5da8dSAndroid Build Coastguard Worker            result = (datetime.datetime(2001, 1, 1) +
560*cda5da8dSAndroid Build Coastguard Worker                      datetime.timedelta(seconds=f))
561*cda5da8dSAndroid Build Coastguard Worker
562*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0x40:  # data
563*cda5da8dSAndroid Build Coastguard Worker            s = self._get_size(tokenL)
564*cda5da8dSAndroid Build Coastguard Worker            result = self._fp.read(s)
565*cda5da8dSAndroid Build Coastguard Worker            if len(result) != s:
566*cda5da8dSAndroid Build Coastguard Worker                raise InvalidFileException()
567*cda5da8dSAndroid Build Coastguard Worker
568*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0x50:  # ascii string
569*cda5da8dSAndroid Build Coastguard Worker            s = self._get_size(tokenL)
570*cda5da8dSAndroid Build Coastguard Worker            data = self._fp.read(s)
571*cda5da8dSAndroid Build Coastguard Worker            if len(data) != s:
572*cda5da8dSAndroid Build Coastguard Worker                raise InvalidFileException()
573*cda5da8dSAndroid Build Coastguard Worker            result = data.decode('ascii')
574*cda5da8dSAndroid Build Coastguard Worker
575*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0x60:  # unicode string
576*cda5da8dSAndroid Build Coastguard Worker            s = self._get_size(tokenL) * 2
577*cda5da8dSAndroid Build Coastguard Worker            data = self._fp.read(s)
578*cda5da8dSAndroid Build Coastguard Worker            if len(data) != s:
579*cda5da8dSAndroid Build Coastguard Worker                raise InvalidFileException()
580*cda5da8dSAndroid Build Coastguard Worker            result = data.decode('utf-16be')
581*cda5da8dSAndroid Build Coastguard Worker
582*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0x80:  # UID
583*cda5da8dSAndroid Build Coastguard Worker            # used by Key-Archiver plist files
584*cda5da8dSAndroid Build Coastguard Worker            result = UID(int.from_bytes(self._fp.read(1 + tokenL), 'big'))
585*cda5da8dSAndroid Build Coastguard Worker
586*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0xA0:  # array
587*cda5da8dSAndroid Build Coastguard Worker            s = self._get_size(tokenL)
588*cda5da8dSAndroid Build Coastguard Worker            obj_refs = self._read_refs(s)
589*cda5da8dSAndroid Build Coastguard Worker            result = []
590*cda5da8dSAndroid Build Coastguard Worker            self._objects[ref] = result
591*cda5da8dSAndroid Build Coastguard Worker            result.extend(self._read_object(x) for x in obj_refs)
592*cda5da8dSAndroid Build Coastguard Worker
593*cda5da8dSAndroid Build Coastguard Worker        # tokenH == 0xB0 is documented as 'ordset', but is not actually
594*cda5da8dSAndroid Build Coastguard Worker        # implemented in the Apple reference code.
595*cda5da8dSAndroid Build Coastguard Worker
596*cda5da8dSAndroid Build Coastguard Worker        # tokenH == 0xC0 is documented as 'set', but sets cannot be used in
597*cda5da8dSAndroid Build Coastguard Worker        # plists.
598*cda5da8dSAndroid Build Coastguard Worker
599*cda5da8dSAndroid Build Coastguard Worker        elif tokenH == 0xD0:  # dict
600*cda5da8dSAndroid Build Coastguard Worker            s = self._get_size(tokenL)
601*cda5da8dSAndroid Build Coastguard Worker            key_refs = self._read_refs(s)
602*cda5da8dSAndroid Build Coastguard Worker            obj_refs = self._read_refs(s)
603*cda5da8dSAndroid Build Coastguard Worker            result = self._dict_type()
604*cda5da8dSAndroid Build Coastguard Worker            self._objects[ref] = result
605*cda5da8dSAndroid Build Coastguard Worker            try:
606*cda5da8dSAndroid Build Coastguard Worker                for k, o in zip(key_refs, obj_refs):
607*cda5da8dSAndroid Build Coastguard Worker                    result[self._read_object(k)] = self._read_object(o)
608*cda5da8dSAndroid Build Coastguard Worker            except TypeError:
609*cda5da8dSAndroid Build Coastguard Worker                raise InvalidFileException()
610*cda5da8dSAndroid Build Coastguard Worker        else:
611*cda5da8dSAndroid Build Coastguard Worker            raise InvalidFileException()
612*cda5da8dSAndroid Build Coastguard Worker
613*cda5da8dSAndroid Build Coastguard Worker        self._objects[ref] = result
614*cda5da8dSAndroid Build Coastguard Worker        return result
615*cda5da8dSAndroid Build Coastguard Worker
616*cda5da8dSAndroid Build Coastguard Workerdef _count_to_size(count):
617*cda5da8dSAndroid Build Coastguard Worker    if count < 1 << 8:
618*cda5da8dSAndroid Build Coastguard Worker        return 1
619*cda5da8dSAndroid Build Coastguard Worker
620*cda5da8dSAndroid Build Coastguard Worker    elif count < 1 << 16:
621*cda5da8dSAndroid Build Coastguard Worker        return 2
622*cda5da8dSAndroid Build Coastguard Worker
623*cda5da8dSAndroid Build Coastguard Worker    elif count < 1 << 32:
624*cda5da8dSAndroid Build Coastguard Worker        return 4
625*cda5da8dSAndroid Build Coastguard Worker
626*cda5da8dSAndroid Build Coastguard Worker    else:
627*cda5da8dSAndroid Build Coastguard Worker        return 8
628*cda5da8dSAndroid Build Coastguard Worker
629*cda5da8dSAndroid Build Coastguard Worker_scalars = (str, int, float, datetime.datetime, bytes)
630*cda5da8dSAndroid Build Coastguard Worker
631*cda5da8dSAndroid Build Coastguard Workerclass _BinaryPlistWriter (object):
632*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, fp, sort_keys, skipkeys):
633*cda5da8dSAndroid Build Coastguard Worker        self._fp = fp
634*cda5da8dSAndroid Build Coastguard Worker        self._sort_keys = sort_keys
635*cda5da8dSAndroid Build Coastguard Worker        self._skipkeys = skipkeys
636*cda5da8dSAndroid Build Coastguard Worker
637*cda5da8dSAndroid Build Coastguard Worker    def write(self, value):
638*cda5da8dSAndroid Build Coastguard Worker
639*cda5da8dSAndroid Build Coastguard Worker        # Flattened object list:
640*cda5da8dSAndroid Build Coastguard Worker        self._objlist = []
641*cda5da8dSAndroid Build Coastguard Worker
642*cda5da8dSAndroid Build Coastguard Worker        # Mappings from object->objectid
643*cda5da8dSAndroid Build Coastguard Worker        # First dict has (type(object), object) as the key,
644*cda5da8dSAndroid Build Coastguard Worker        # second dict is used when object is not hashable and
645*cda5da8dSAndroid Build Coastguard Worker        # has id(object) as the key.
646*cda5da8dSAndroid Build Coastguard Worker        self._objtable = {}
647*cda5da8dSAndroid Build Coastguard Worker        self._objidtable = {}
648*cda5da8dSAndroid Build Coastguard Worker
649*cda5da8dSAndroid Build Coastguard Worker        # Create list of all objects in the plist
650*cda5da8dSAndroid Build Coastguard Worker        self._flatten(value)
651*cda5da8dSAndroid Build Coastguard Worker
652*cda5da8dSAndroid Build Coastguard Worker        # Size of object references in serialized containers
653*cda5da8dSAndroid Build Coastguard Worker        # depends on the number of objects in the plist.
654*cda5da8dSAndroid Build Coastguard Worker        num_objects = len(self._objlist)
655*cda5da8dSAndroid Build Coastguard Worker        self._object_offsets = [0]*num_objects
656*cda5da8dSAndroid Build Coastguard Worker        self._ref_size = _count_to_size(num_objects)
657*cda5da8dSAndroid Build Coastguard Worker
658*cda5da8dSAndroid Build Coastguard Worker        self._ref_format = _BINARY_FORMAT[self._ref_size]
659*cda5da8dSAndroid Build Coastguard Worker
660*cda5da8dSAndroid Build Coastguard Worker        # Write file header
661*cda5da8dSAndroid Build Coastguard Worker        self._fp.write(b'bplist00')
662*cda5da8dSAndroid Build Coastguard Worker
663*cda5da8dSAndroid Build Coastguard Worker        # Write object list
664*cda5da8dSAndroid Build Coastguard Worker        for obj in self._objlist:
665*cda5da8dSAndroid Build Coastguard Worker            self._write_object(obj)
666*cda5da8dSAndroid Build Coastguard Worker
667*cda5da8dSAndroid Build Coastguard Worker        # Write refnum->object offset table
668*cda5da8dSAndroid Build Coastguard Worker        top_object = self._getrefnum(value)
669*cda5da8dSAndroid Build Coastguard Worker        offset_table_offset = self._fp.tell()
670*cda5da8dSAndroid Build Coastguard Worker        offset_size = _count_to_size(offset_table_offset)
671*cda5da8dSAndroid Build Coastguard Worker        offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects
672*cda5da8dSAndroid Build Coastguard Worker        self._fp.write(struct.pack(offset_format, *self._object_offsets))
673*cda5da8dSAndroid Build Coastguard Worker
674*cda5da8dSAndroid Build Coastguard Worker        # Write trailer
675*cda5da8dSAndroid Build Coastguard Worker        sort_version = 0
676*cda5da8dSAndroid Build Coastguard Worker        trailer = (
677*cda5da8dSAndroid Build Coastguard Worker            sort_version, offset_size, self._ref_size, num_objects,
678*cda5da8dSAndroid Build Coastguard Worker            top_object, offset_table_offset
679*cda5da8dSAndroid Build Coastguard Worker        )
680*cda5da8dSAndroid Build Coastguard Worker        self._fp.write(struct.pack('>5xBBBQQQ', *trailer))
681*cda5da8dSAndroid Build Coastguard Worker
682*cda5da8dSAndroid Build Coastguard Worker    def _flatten(self, value):
683*cda5da8dSAndroid Build Coastguard Worker        # First check if the object is in the object table, not used for
684*cda5da8dSAndroid Build Coastguard Worker        # containers to ensure that two subcontainers with the same contents
685*cda5da8dSAndroid Build Coastguard Worker        # will be serialized as distinct values.
686*cda5da8dSAndroid Build Coastguard Worker        if isinstance(value, _scalars):
687*cda5da8dSAndroid Build Coastguard Worker            if (type(value), value) in self._objtable:
688*cda5da8dSAndroid Build Coastguard Worker                return
689*cda5da8dSAndroid Build Coastguard Worker
690*cda5da8dSAndroid Build Coastguard Worker        elif id(value) in self._objidtable:
691*cda5da8dSAndroid Build Coastguard Worker            return
692*cda5da8dSAndroid Build Coastguard Worker
693*cda5da8dSAndroid Build Coastguard Worker        # Add to objectreference map
694*cda5da8dSAndroid Build Coastguard Worker        refnum = len(self._objlist)
695*cda5da8dSAndroid Build Coastguard Worker        self._objlist.append(value)
696*cda5da8dSAndroid Build Coastguard Worker        if isinstance(value, _scalars):
697*cda5da8dSAndroid Build Coastguard Worker            self._objtable[(type(value), value)] = refnum
698*cda5da8dSAndroid Build Coastguard Worker        else:
699*cda5da8dSAndroid Build Coastguard Worker            self._objidtable[id(value)] = refnum
700*cda5da8dSAndroid Build Coastguard Worker
701*cda5da8dSAndroid Build Coastguard Worker        # And finally recurse into containers
702*cda5da8dSAndroid Build Coastguard Worker        if isinstance(value, dict):
703*cda5da8dSAndroid Build Coastguard Worker            keys = []
704*cda5da8dSAndroid Build Coastguard Worker            values = []
705*cda5da8dSAndroid Build Coastguard Worker            items = value.items()
706*cda5da8dSAndroid Build Coastguard Worker            if self._sort_keys:
707*cda5da8dSAndroid Build Coastguard Worker                items = sorted(items)
708*cda5da8dSAndroid Build Coastguard Worker
709*cda5da8dSAndroid Build Coastguard Worker            for k, v in items:
710*cda5da8dSAndroid Build Coastguard Worker                if not isinstance(k, str):
711*cda5da8dSAndroid Build Coastguard Worker                    if self._skipkeys:
712*cda5da8dSAndroid Build Coastguard Worker                        continue
713*cda5da8dSAndroid Build Coastguard Worker                    raise TypeError("keys must be strings")
714*cda5da8dSAndroid Build Coastguard Worker                keys.append(k)
715*cda5da8dSAndroid Build Coastguard Worker                values.append(v)
716*cda5da8dSAndroid Build Coastguard Worker
717*cda5da8dSAndroid Build Coastguard Worker            for o in itertools.chain(keys, values):
718*cda5da8dSAndroid Build Coastguard Worker                self._flatten(o)
719*cda5da8dSAndroid Build Coastguard Worker
720*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, (list, tuple)):
721*cda5da8dSAndroid Build Coastguard Worker            for o in value:
722*cda5da8dSAndroid Build Coastguard Worker                self._flatten(o)
723*cda5da8dSAndroid Build Coastguard Worker
724*cda5da8dSAndroid Build Coastguard Worker    def _getrefnum(self, value):
725*cda5da8dSAndroid Build Coastguard Worker        if isinstance(value, _scalars):
726*cda5da8dSAndroid Build Coastguard Worker            return self._objtable[(type(value), value)]
727*cda5da8dSAndroid Build Coastguard Worker        else:
728*cda5da8dSAndroid Build Coastguard Worker            return self._objidtable[id(value)]
729*cda5da8dSAndroid Build Coastguard Worker
730*cda5da8dSAndroid Build Coastguard Worker    def _write_size(self, token, size):
731*cda5da8dSAndroid Build Coastguard Worker        if size < 15:
732*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>B', token | size))
733*cda5da8dSAndroid Build Coastguard Worker
734*cda5da8dSAndroid Build Coastguard Worker        elif size < 1 << 8:
735*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size))
736*cda5da8dSAndroid Build Coastguard Worker
737*cda5da8dSAndroid Build Coastguard Worker        elif size < 1 << 16:
738*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size))
739*cda5da8dSAndroid Build Coastguard Worker
740*cda5da8dSAndroid Build Coastguard Worker        elif size < 1 << 32:
741*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size))
742*cda5da8dSAndroid Build Coastguard Worker
743*cda5da8dSAndroid Build Coastguard Worker        else:
744*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size))
745*cda5da8dSAndroid Build Coastguard Worker
746*cda5da8dSAndroid Build Coastguard Worker    def _write_object(self, value):
747*cda5da8dSAndroid Build Coastguard Worker        ref = self._getrefnum(value)
748*cda5da8dSAndroid Build Coastguard Worker        self._object_offsets[ref] = self._fp.tell()
749*cda5da8dSAndroid Build Coastguard Worker        if value is None:
750*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(b'\x00')
751*cda5da8dSAndroid Build Coastguard Worker
752*cda5da8dSAndroid Build Coastguard Worker        elif value is False:
753*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(b'\x08')
754*cda5da8dSAndroid Build Coastguard Worker
755*cda5da8dSAndroid Build Coastguard Worker        elif value is True:
756*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(b'\x09')
757*cda5da8dSAndroid Build Coastguard Worker
758*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, int):
759*cda5da8dSAndroid Build Coastguard Worker            if value < 0:
760*cda5da8dSAndroid Build Coastguard Worker                try:
761*cda5da8dSAndroid Build Coastguard Worker                    self._fp.write(struct.pack('>Bq', 0x13, value))
762*cda5da8dSAndroid Build Coastguard Worker                except struct.error:
763*cda5da8dSAndroid Build Coastguard Worker                    raise OverflowError(value) from None
764*cda5da8dSAndroid Build Coastguard Worker            elif value < 1 << 8:
765*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BB', 0x10, value))
766*cda5da8dSAndroid Build Coastguard Worker            elif value < 1 << 16:
767*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BH', 0x11, value))
768*cda5da8dSAndroid Build Coastguard Worker            elif value < 1 << 32:
769*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BL', 0x12, value))
770*cda5da8dSAndroid Build Coastguard Worker            elif value < 1 << 63:
771*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BQ', 0x13, value))
772*cda5da8dSAndroid Build Coastguard Worker            elif value < 1 << 64:
773*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(b'\x14' + value.to_bytes(16, 'big', signed=True))
774*cda5da8dSAndroid Build Coastguard Worker            else:
775*cda5da8dSAndroid Build Coastguard Worker                raise OverflowError(value)
776*cda5da8dSAndroid Build Coastguard Worker
777*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, float):
778*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>Bd', 0x23, value))
779*cda5da8dSAndroid Build Coastguard Worker
780*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, datetime.datetime):
781*cda5da8dSAndroid Build Coastguard Worker            f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
782*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>Bd', 0x33, f))
783*cda5da8dSAndroid Build Coastguard Worker
784*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, (bytes, bytearray)):
785*cda5da8dSAndroid Build Coastguard Worker            self._write_size(0x40, len(value))
786*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(value)
787*cda5da8dSAndroid Build Coastguard Worker
788*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, str):
789*cda5da8dSAndroid Build Coastguard Worker            try:
790*cda5da8dSAndroid Build Coastguard Worker                t = value.encode('ascii')
791*cda5da8dSAndroid Build Coastguard Worker                self._write_size(0x50, len(value))
792*cda5da8dSAndroid Build Coastguard Worker            except UnicodeEncodeError:
793*cda5da8dSAndroid Build Coastguard Worker                t = value.encode('utf-16be')
794*cda5da8dSAndroid Build Coastguard Worker                self._write_size(0x60, len(t) // 2)
795*cda5da8dSAndroid Build Coastguard Worker
796*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(t)
797*cda5da8dSAndroid Build Coastguard Worker
798*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, UID):
799*cda5da8dSAndroid Build Coastguard Worker            if value.data < 0:
800*cda5da8dSAndroid Build Coastguard Worker                raise ValueError("UIDs must be positive")
801*cda5da8dSAndroid Build Coastguard Worker            elif value.data < 1 << 8:
802*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BB', 0x80, value))
803*cda5da8dSAndroid Build Coastguard Worker            elif value.data < 1 << 16:
804*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BH', 0x81, value))
805*cda5da8dSAndroid Build Coastguard Worker            elif value.data < 1 << 32:
806*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BL', 0x83, value))
807*cda5da8dSAndroid Build Coastguard Worker            elif value.data < 1 << 64:
808*cda5da8dSAndroid Build Coastguard Worker                self._fp.write(struct.pack('>BQ', 0x87, value))
809*cda5da8dSAndroid Build Coastguard Worker            else:
810*cda5da8dSAndroid Build Coastguard Worker                raise OverflowError(value)
811*cda5da8dSAndroid Build Coastguard Worker
812*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, (list, tuple)):
813*cda5da8dSAndroid Build Coastguard Worker            refs = [self._getrefnum(o) for o in value]
814*cda5da8dSAndroid Build Coastguard Worker            s = len(refs)
815*cda5da8dSAndroid Build Coastguard Worker            self._write_size(0xA0, s)
816*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>' + self._ref_format * s, *refs))
817*cda5da8dSAndroid Build Coastguard Worker
818*cda5da8dSAndroid Build Coastguard Worker        elif isinstance(value, dict):
819*cda5da8dSAndroid Build Coastguard Worker            keyRefs, valRefs = [], []
820*cda5da8dSAndroid Build Coastguard Worker
821*cda5da8dSAndroid Build Coastguard Worker            if self._sort_keys:
822*cda5da8dSAndroid Build Coastguard Worker                rootItems = sorted(value.items())
823*cda5da8dSAndroid Build Coastguard Worker            else:
824*cda5da8dSAndroid Build Coastguard Worker                rootItems = value.items()
825*cda5da8dSAndroid Build Coastguard Worker
826*cda5da8dSAndroid Build Coastguard Worker            for k, v in rootItems:
827*cda5da8dSAndroid Build Coastguard Worker                if not isinstance(k, str):
828*cda5da8dSAndroid Build Coastguard Worker                    if self._skipkeys:
829*cda5da8dSAndroid Build Coastguard Worker                        continue
830*cda5da8dSAndroid Build Coastguard Worker                    raise TypeError("keys must be strings")
831*cda5da8dSAndroid Build Coastguard Worker                keyRefs.append(self._getrefnum(k))
832*cda5da8dSAndroid Build Coastguard Worker                valRefs.append(self._getrefnum(v))
833*cda5da8dSAndroid Build Coastguard Worker
834*cda5da8dSAndroid Build Coastguard Worker            s = len(keyRefs)
835*cda5da8dSAndroid Build Coastguard Worker            self._write_size(0xD0, s)
836*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs))
837*cda5da8dSAndroid Build Coastguard Worker            self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs))
838*cda5da8dSAndroid Build Coastguard Worker
839*cda5da8dSAndroid Build Coastguard Worker        else:
840*cda5da8dSAndroid Build Coastguard Worker            raise TypeError(value)
841*cda5da8dSAndroid Build Coastguard Worker
842*cda5da8dSAndroid Build Coastguard Worker
843*cda5da8dSAndroid Build Coastguard Workerdef _is_fmt_binary(header):
844*cda5da8dSAndroid Build Coastguard Worker    return header[:8] == b'bplist00'
845*cda5da8dSAndroid Build Coastguard Worker
846*cda5da8dSAndroid Build Coastguard Worker
847*cda5da8dSAndroid Build Coastguard Worker#
848*cda5da8dSAndroid Build Coastguard Worker# Generic bits
849*cda5da8dSAndroid Build Coastguard Worker#
850*cda5da8dSAndroid Build Coastguard Worker
851*cda5da8dSAndroid Build Coastguard Worker_FORMATS={
852*cda5da8dSAndroid Build Coastguard Worker    FMT_XML: dict(
853*cda5da8dSAndroid Build Coastguard Worker        detect=_is_fmt_xml,
854*cda5da8dSAndroid Build Coastguard Worker        parser=_PlistParser,
855*cda5da8dSAndroid Build Coastguard Worker        writer=_PlistWriter,
856*cda5da8dSAndroid Build Coastguard Worker    ),
857*cda5da8dSAndroid Build Coastguard Worker    FMT_BINARY: dict(
858*cda5da8dSAndroid Build Coastguard Worker        detect=_is_fmt_binary,
859*cda5da8dSAndroid Build Coastguard Worker        parser=_BinaryPlistParser,
860*cda5da8dSAndroid Build Coastguard Worker        writer=_BinaryPlistWriter,
861*cda5da8dSAndroid Build Coastguard Worker    )
862*cda5da8dSAndroid Build Coastguard Worker}
863*cda5da8dSAndroid Build Coastguard Worker
864*cda5da8dSAndroid Build Coastguard Worker
865*cda5da8dSAndroid Build Coastguard Workerdef load(fp, *, fmt=None, dict_type=dict):
866*cda5da8dSAndroid Build Coastguard Worker    """Read a .plist file. 'fp' should be a readable and binary file object.
867*cda5da8dSAndroid Build Coastguard Worker    Return the unpacked root object (which usually is a dictionary).
868*cda5da8dSAndroid Build Coastguard Worker    """
869*cda5da8dSAndroid Build Coastguard Worker    if fmt is None:
870*cda5da8dSAndroid Build Coastguard Worker        header = fp.read(32)
871*cda5da8dSAndroid Build Coastguard Worker        fp.seek(0)
872*cda5da8dSAndroid Build Coastguard Worker        for info in _FORMATS.values():
873*cda5da8dSAndroid Build Coastguard Worker            if info['detect'](header):
874*cda5da8dSAndroid Build Coastguard Worker                P = info['parser']
875*cda5da8dSAndroid Build Coastguard Worker                break
876*cda5da8dSAndroid Build Coastguard Worker
877*cda5da8dSAndroid Build Coastguard Worker        else:
878*cda5da8dSAndroid Build Coastguard Worker            raise InvalidFileException()
879*cda5da8dSAndroid Build Coastguard Worker
880*cda5da8dSAndroid Build Coastguard Worker    else:
881*cda5da8dSAndroid Build Coastguard Worker        P = _FORMATS[fmt]['parser']
882*cda5da8dSAndroid Build Coastguard Worker
883*cda5da8dSAndroid Build Coastguard Worker    p = P(dict_type=dict_type)
884*cda5da8dSAndroid Build Coastguard Worker    return p.parse(fp)
885*cda5da8dSAndroid Build Coastguard Worker
886*cda5da8dSAndroid Build Coastguard Worker
887*cda5da8dSAndroid Build Coastguard Workerdef loads(value, *, fmt=None, dict_type=dict):
888*cda5da8dSAndroid Build Coastguard Worker    """Read a .plist file from a bytes object.
889*cda5da8dSAndroid Build Coastguard Worker    Return the unpacked root object (which usually is a dictionary).
890*cda5da8dSAndroid Build Coastguard Worker    """
891*cda5da8dSAndroid Build Coastguard Worker    fp = BytesIO(value)
892*cda5da8dSAndroid Build Coastguard Worker    return load(fp, fmt=fmt, dict_type=dict_type)
893*cda5da8dSAndroid Build Coastguard Worker
894*cda5da8dSAndroid Build Coastguard Worker
895*cda5da8dSAndroid Build Coastguard Workerdef dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False):
896*cda5da8dSAndroid Build Coastguard Worker    """Write 'value' to a .plist file. 'fp' should be a writable,
897*cda5da8dSAndroid Build Coastguard Worker    binary file object.
898*cda5da8dSAndroid Build Coastguard Worker    """
899*cda5da8dSAndroid Build Coastguard Worker    if fmt not in _FORMATS:
900*cda5da8dSAndroid Build Coastguard Worker        raise ValueError("Unsupported format: %r"%(fmt,))
901*cda5da8dSAndroid Build Coastguard Worker
902*cda5da8dSAndroid Build Coastguard Worker    writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys)
903*cda5da8dSAndroid Build Coastguard Worker    writer.write(value)
904*cda5da8dSAndroid Build Coastguard Worker
905*cda5da8dSAndroid Build Coastguard Worker
906*cda5da8dSAndroid Build Coastguard Workerdef dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True):
907*cda5da8dSAndroid Build Coastguard Worker    """Return a bytes object with the contents for a .plist file.
908*cda5da8dSAndroid Build Coastguard Worker    """
909*cda5da8dSAndroid Build Coastguard Worker    fp = BytesIO()
910*cda5da8dSAndroid Build Coastguard Worker    dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys)
911*cda5da8dSAndroid Build Coastguard Worker    return fp.getvalue()
912