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("&", "&") # escape '&' 169*cda5da8dSAndroid Build Coastguard Worker text = text.replace("<", "<") # escape '<' 170*cda5da8dSAndroid Build Coastguard Worker text = text.replace(">", ">") # 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