diff -r 9986ec0e559a -r 10516539f238 Utilities/binplistlib.py --- a/Utilities/binplistlib.py Tue Oct 15 22:03:54 2013 +0200 +++ b/Utilities/binplistlib.py Fri Oct 18 23:00:41 2013 +0200 @@ -4,7 +4,8 @@ # """ -Module implementing a library for reading and writing binary property list files. +Module implementing a library for reading and writing binary property list +files. Binary Property List (plist) files provide a faster and smaller serialization format for property lists on OS X. This is a library for generating binary @@ -79,14 +80,15 @@ # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. # from collections import namedtuple @@ -112,12 +114,18 @@ This is used in keyed archiving. """ def __repr__(self): + """ + Public method to return an object representation. + + @return object representation (string) + """ return "Uid(%d)" % self class Data(bytes): """ - Class implementing a wrapper around bytes types for representing Data values. + Class implementing a wrapper around bytes types for representing Data + values. """ pass @@ -140,7 +148,8 @@ """ Module function to read a plist file. - @param pathOrFile name of the plist file (string) or an open file (file object) + @param pathOrFile name of the plist file (string) or an open file + (file object) @return reference to the read object @exception InvalidPlistException raised to signal an invalid plist file """ @@ -168,8 +177,10 @@ Module function to write a plist file. @param rootObject reference to the object to be written - @param pathOrFile name of the plist file (string) or an open file (file object) - @param binary flag indicating the generation of a binary plist file (boolean) + @param pathOrFile name of the plist file (string) or an open file + (file object) + @param binary flag indicating the generation of a binary plist file + (boolean) """ if not binary: plistlib.writePlist(rootObject, pathOrFile) @@ -192,7 +203,6 @@ @param data plist data (bytes) @return reference to the read object - @exception InvalidPlistException raised to signal an invalid plist file """ return readPlist(BytesIO(data)) @@ -202,7 +212,9 @@ Module function to write a plist bytes object. @param rootObject reference to the object to be written - @param binary flag indicating the generation of a binary plist bytes object (boolean) + @param binary flag indicating the generation of a binary plist bytes + object (boolean) + @return bytes object containing the plist data """ if not binary: return plistlib.writePlistToBytes(rootObject) @@ -228,10 +240,11 @@ return False PlistTrailer = namedtuple('PlistTrailer', - 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber, offsetTableOffset') + 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber,' + ' offsetTableOffset') PlistByteCounts = namedtuple('PlistByteCounts', - 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes, stringBytes, ' - 'uidBytes, arrayBytes, setBytes, dictBytes') + 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes,' + ' stringBytes, uidBytes, arrayBytes, setBytes, dictBytes') class PlistReader(object): @@ -275,6 +288,8 @@ Private method to read the root object. @return unpickled object + @exception InvalidPlistException raised to indicate an invalid + plist file """ result = None self.reset() @@ -287,18 +302,22 @@ raise InvalidPlistException("File is too short.") trailerContents = self.contents[-32:] try: - self.trailer = PlistTrailer._make(unpack("!xxxxxxBBQQQ", trailerContents)) + self.trailer = PlistTrailer._make( + unpack("!xxxxxxBBQQQ", trailerContents)) offset_size = self.trailer.offsetSize * self.trailer.offsetCount offset = self.trailer.offsetTableOffset offset_contents = self.contents[offset:offset + offset_size] offset_i = 0 while offset_i < self.trailer.offsetCount: begin = self.trailer.offsetSize * offset_i - tmp_contents = offset_contents[begin:begin + self.trailer.offsetSize] - tmp_sized = self.getSizedInteger(tmp_contents, self.trailer.offsetSize) + tmp_contents = offset_contents[ + begin:begin + self.trailer.offsetSize] + tmp_sized = self.getSizedInteger( + tmp_contents, self.trailer.offsetSize) self.offsets.append(tmp_sized) offset_i += 1 - self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber) + self.setCurrentOffsetToObjectNumber( + self.trailer.topLevelObjectNumber) result = self.readObject() except TypeError as e: raise InvalidPlistException(e) @@ -317,6 +336,8 @@ Private method to read the object data. @return unpickled object + @exception InvalidPlistException raised to indicate an invalid + plist file """ result = None tmp_byte = self.contents[self.currentOffset:self.currentOffset + 1] @@ -343,7 +364,8 @@ pass # fill byte else: raise InvalidPlistException( - "Invalid object found at offset: {0}".format(self.currentOffset - 1)) + "Invalid object found at offset: {0}".format( + self.currentOffset - 1)) # int elif format == 0b0001: extra = proc_extra(extra) @@ -408,6 +430,8 @@ @param length length of the object (integer) @return float object + @exception InvalidPlistException raised to indicate an invalid + plist file """ result = 0.0 to_read = pow(2, length) @@ -432,7 +456,8 @@ i = 0 while i < count: fragment = self.contents[ - self.currentOffset:self.currentOffset + self.trailer.objectRefSize] + self.currentOffset: + self.currentOffset + self.trailer.objectRefSize] ref = self.getSizedInteger(fragment, len(fragment)) refs.append(ref) self.currentOffset += self.trailer.objectRefSize @@ -497,7 +522,8 @@ @return unicode encoded string """ actual_length = length * 2 - data = self.contents[self.currentOffset:self.currentOffset + actual_length] + data = self.contents[ + self.currentOffset:self.currentOffset + actual_length] # unpack not needed?!! data = unpack(">%ds" % (actual_length), data)[0] self.currentOffset += actual_length return data.decode('utf_16_be') @@ -509,8 +535,10 @@ @return date object (datetime.datetime) """ global apple_reference_date_offset - result = unpack(">d", self.contents[self.currentOffset:self.currentOffset + 8])[0] - result = datetime.datetime.utcfromtimestamp(result + apple_reference_date_offset) + result = unpack(">d", + self.contents[self.currentOffset:self.currentOffset + 8])[0] + result = datetime.datetime.utcfromtimestamp( + result + apple_reference_date_offset) self.currentOffset += 8 return result @@ -540,6 +568,9 @@ @param data data to extract the integer from (bytes) @param bytes length of the integer (integer) + @return read integer (integer) + @exception InvalidPlistException raised to indicate an invalid + plist file """ result = 0 # 1, 2, and 4 byte integers are unsigned @@ -552,7 +583,8 @@ elif bytes == 8: result = unpack('>q', data)[0] else: - raise InvalidPlistException("Encountered integer longer than 8 bytes.") + raise InvalidPlistException( + "Encountered integer longer than 8 bytes.") return result @@ -561,9 +593,19 @@ Class wrapping a hashable value. """ def __init__(self, value): + """ + Constructor + + @param value object value + """ self.value = value def __repr__(self): + """ + Public method to generate a representation of the object. + + @return object representation (string) + """ return "<HashableWrapper: %s>" % [self.value] @@ -572,9 +614,19 @@ Class wrapping a boolean value. """ def __init__(self, value): + """ + Constructor + + @param value object value (boolean) + """ self.value = value def __repr__(self): + """ + Public method to generate a representation of the object. + + @return object representation (string) + """ return "<BoolWrapper: %s>" % self.value @@ -624,6 +676,7 @@ If the given object has been written already, return its position in the offset table. Otherwise, return None. + @param obj object @return position of the object (integer) """ return self.writtenReferences.get(obj) @@ -656,12 +709,14 @@ """ output = self.header wrapped_root = self.wrapRoot(root) - should_reference_root = True # not isinstance(wrapped_root, HashableWrapper) - self.computeOffsets(wrapped_root, asReference=should_reference_root, isRoot=True) + should_reference_root = True + self.computeOffsets( + wrapped_root, asReference=should_reference_root, isRoot=True) self.trailer = self.trailer._replace( **{'objectRefSize': self.intSize(len(self.computedUniques))}) (_, output) = self.writeObjectReference(wrapped_root, output) - output = self.writeObject(wrapped_root, output, setReferencePosition=True) + output = self.writeObject( + wrapped_root, output, setReferencePosition=True) # output size at this point is an upper bound on how big the # object reference offsets need to be. @@ -710,15 +765,32 @@ return root def incrementByteCount(self, field, incr=1): + """ + Public method to increment the byte count. + + @param field field to evaluate + @param incr byte count increment (integer) + """ self.byteCounts = self.byteCounts._replace( **{field: self.byteCounts.__getattribute__(field) + incr}) def computeOffsets(self, obj, asReference=False, isRoot=False): + """ + Public method to compute offsets of an object. + + @param obj plist object + @param asReference flag indicating offsets as references (boolean) + @param isRoot flag indicating a root object (boolean) + @exception InvalidPlistException raised to indicate an invalid + plist file + """ # __IGNORE_WARNING__ def check_key(key): if key is None: - raise InvalidPlistException('Dictionary keys cannot be null in plists.') + raise InvalidPlistException( + 'Dictionary keys cannot be null in plists.') elif isinstance(key, Data): - raise InvalidPlistException('Data cannot be dictionary keys in plists.') + raise InvalidPlistException( + 'Data cannot be dictionary keys in plists.') elif not isinstance(key, str): raise InvalidPlistException('Keys must be strings.') @@ -726,6 +798,7 @@ if size > 0b1110: size += self.intSize(size) return size + # If this should be a reference, then we keep a record of it in the # uniques table. if asReference: @@ -798,7 +871,8 @@ bytes=self.trailer.objectRefSize) return (True, output) else: - output += self.binaryInt(position, bytes=self.trailer.objectRefSize) + output += self.binaryInt( + position, bytes=self.trailer.objectRefSize) return (False, output) def writeObject(self, obj, output, setReferencePosition=False): @@ -870,7 +944,8 @@ if isNew: objectsToWrite.append(objRef) for objRef in objectsToWrite: - output = self.writeObject(objRef, output, setReferencePosition=True) + output = self.writeObject( + objRef, output, setReferencePosition=True) elif isinstance(obj, dict): output += proc_variable_length(0b1101, len(obj)) keys = [] @@ -888,7 +963,8 @@ if isNew: objectsToWrite.append(value) for objRef in objectsToWrite: - output = self.writeObject(objRef, output, setReferencePosition=True) + output = self.writeObject( + objRef, output, setReferencePosition=True) return output def writeOffsetTable(self, output): @@ -897,6 +973,8 @@ @param output current output (bytes) @return new output (bytes) + @exception InvalidPlistException raised to indicate an invalid + plist file """ all_positions = [] writtenReferences = list(self.writtenReferences.items()) @@ -929,6 +1007,8 @@ @param obj integer to be packed @param bytes length the integer should be packed into (integer) @return serialized object (bytes) + @exception InvalidPlistException raised to indicate an invalid + plist file """ result = '' if bytes is None: @@ -943,7 +1023,8 @@ result += pack('>q', obj) else: raise InvalidPlistException( - "Core Foundation can't handle integers with size greater than 8 bytes.") + "Core Foundation can't handle integers with size greater" + " than 8 bytes.") return result def intSize(self, obj): @@ -953,6 +1034,8 @@ @param obj integer object @return number of bytes required (integer) + @exception InvalidPlistException raised to indicate an invalid + plist file """ # SIGNED if obj < 0: # Signed integer, always 8 bytes @@ -970,7 +1053,8 @@ return 8 else: raise InvalidPlistException( - "Core Foundation can't handle integers with size greater than 8 bytes.") + "Core Foundation can't handle integers with size greater" + " than 8 bytes.") def realSize(self, obj): """