ยปCore Development>Code coverage>Lib/plistlib.py

Python code coverage for Lib/plistlib.py

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