66 # this list of conditions and the following disclaimer. |
66 # this list of conditions and the following disclaimer. |
67 # * Redistributions in binary form must reproduce the above copyright |
67 # * Redistributions in binary form must reproduce the above copyright |
68 # notice, this list of conditions and the following disclaimer in the |
68 # notice, this list of conditions and the following disclaimer in the |
69 # documentation and/or other materials provided with the distribution. |
69 # documentation and/or other materials provided with the distribution. |
70 # * Neither the name of biplist nor the names of its contributors may be |
70 # * Neither the name of biplist nor the names of its contributors may be |
71 # used to endorse or promote products derived from this software without |
71 # used to endorse or promote products derived from this software without |
72 # specific prior written permission. |
72 # specific prior written permission. |
73 # |
73 # |
74 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
74 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
75 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
75 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
76 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
76 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
176 writer.writeRoot(rootObject) |
177 writer.writeRoot(rootObject) |
177 if didOpen: |
178 if didOpen: |
178 pathOrFile.close() |
179 pathOrFile.close() |
179 return |
180 return |
180 |
181 |
|
182 |
181 def readPlistFromBytes(data): |
183 def readPlistFromBytes(data): |
182 """ |
184 """ |
183 Module function to read from a plist bytes object. |
185 Module function to read from a plist bytes object. |
184 |
186 |
185 @param data plist data (bytes) |
187 @param data plist data (bytes) |
186 @return reference to the read object |
188 @return reference to the read object |
187 @exception InvalidPlistException raised to signal an invalid plist file |
189 @exception InvalidPlistException raised to signal an invalid plist file |
188 """ |
190 """ |
189 return readPlist(BytesIO(data)) |
191 return readPlist(BytesIO(data)) |
|
192 |
190 |
193 |
191 def writePlistToBytes(rootObject, binary=True): |
194 def writePlistToBytes(rootObject, binary=True): |
192 """ |
195 """ |
193 Module function to write a plist bytes object. |
196 Module function to write a plist bytes object. |
194 |
197 |
220 PlistTrailer = namedtuple('PlistTrailer', |
224 PlistTrailer = namedtuple('PlistTrailer', |
221 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber, offsetTableOffset') |
225 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber, offsetTableOffset') |
222 PlistByteCounts = namedtuple('PlistByteCounts', |
226 PlistByteCounts = namedtuple('PlistByteCounts', |
223 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes, stringBytes, ' |
227 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes, stringBytes, ' |
224 'uidBytes, arrayBytes, setBytes, dictBytes') |
228 'uidBytes, arrayBytes, setBytes, dictBytes') |
|
229 |
225 |
230 |
226 class PlistReader(object): |
231 class PlistReader(object): |
227 """ |
232 """ |
228 Class implementing the plist reader. |
233 Class implementing the plist reader. |
229 """ |
234 """ |
277 trailerContents = self.contents[-32:] |
282 trailerContents = self.contents[-32:] |
278 try: |
283 try: |
279 self.trailer = PlistTrailer._make(unpack("!xxxxxxBBQQQ", trailerContents)) |
284 self.trailer = PlistTrailer._make(unpack("!xxxxxxBBQQQ", trailerContents)) |
280 offset_size = self.trailer.offsetSize * self.trailer.offsetCount |
285 offset_size = self.trailer.offsetSize * self.trailer.offsetCount |
281 offset = self.trailer.offsetTableOffset |
286 offset = self.trailer.offsetTableOffset |
282 offset_contents = self.contents[offset:offset+offset_size] |
287 offset_contents = self.contents[offset:offset + offset_size] |
283 offset_i = 0 |
288 offset_i = 0 |
284 while offset_i < self.trailer.offsetCount: |
289 while offset_i < self.trailer.offsetCount: |
285 begin = self.trailer.offsetSize*offset_i |
290 begin = self.trailer.offsetSize * offset_i |
286 tmp_contents = offset_contents[begin:begin+self.trailer.offsetSize] |
291 tmp_contents = offset_contents[begin:begin + self.trailer.offsetSize] |
287 tmp_sized = self.getSizedInteger(tmp_contents, self.trailer.offsetSize) |
292 tmp_sized = self.getSizedInteger(tmp_contents, self.trailer.offsetSize) |
288 self.offsets.append(tmp_sized) |
293 self.offsets.append(tmp_sized) |
289 offset_i += 1 |
294 offset_i += 1 |
290 self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber) |
295 self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber) |
291 result = self.readObject() |
296 result = self.readObject() |
398 @param length length of the object (integer) |
403 @param length length of the object (integer) |
399 @return float object |
404 @return float object |
400 """ |
405 """ |
401 result = 0.0 |
406 result = 0.0 |
402 to_read = pow(2, length) |
407 to_read = pow(2, length) |
403 data = self.contents[self.currentOffset:self.currentOffset+to_read] |
408 data = self.contents[self.currentOffset:self.currentOffset + to_read] |
404 if length == 2: # 4 bytes |
409 if length == 2: # 4 bytes |
405 result = unpack('>f', data)[0] |
410 result = unpack('>f', data)[0] |
406 elif length == 3: # 8 bytes |
411 elif length == 3: # 8 bytes |
407 result = unpack('>d', data)[0] |
412 result = unpack('>d', data)[0] |
408 else: |
413 else: |
409 raise InvalidPlistException( |
414 raise InvalidPlistException( |
410 "Unknown real of length {0} bytes".format(to_read)) |
415 "Unknown real of length {0} bytes".format(to_read)) |
411 return result |
416 return result |
412 |
417 |
413 def readRefs(self, count): |
418 def readRefs(self, count): |
414 """ |
419 """ |
415 Private method to read References. |
420 Private method to read References. |
416 |
421 |
417 @param count amount of the references (integer) |
422 @param count amount of the references (integer) |
418 @return list of references (list of integers) |
423 @return list of references (list of integers) |
419 """ |
424 """ |
420 refs = [] |
425 refs = [] |
421 i = 0 |
426 i = 0 |
422 while i < count: |
427 while i < count: |
423 fragment = self.contents[ |
428 fragment = self.contents[ |
424 self.currentOffset:self.currentOffset+self.trailer.objectRefSize] |
429 self.currentOffset:self.currentOffset + self.trailer.objectRefSize] |
425 ref = self.getSizedInteger(fragment, len(fragment)) |
430 ref = self.getSizedInteger(fragment, len(fragment)) |
426 refs.append(ref) |
431 refs.append(ref) |
427 self.currentOffset += self.trailer.objectRefSize |
432 self.currentOffset += self.trailer.objectRefSize |
428 i += 1 |
433 i += 1 |
429 return refs |
434 return refs |
508 Private method to read some bytes. |
513 Private method to read some bytes. |
509 |
514 |
510 @param length number of bytes to read (integer) |
515 @param length number of bytes to read (integer) |
511 @return Data object |
516 @return Data object |
512 """ |
517 """ |
513 result = self.contents[self.currentOffset:self.currentOffset+length] |
518 result = self.contents[self.currentOffset:self.currentOffset + length] |
514 self.currentOffset += length |
519 self.currentOffset += length |
515 return Data(result) |
520 return Data(result) |
516 |
521 |
517 def readUid(self, length): |
522 def readUid(self, length): |
518 """ |
523 """ |
519 Private method to read a UID. |
524 Private method to read a UID. |
520 |
525 |
521 @param length length of the UID (integer) |
526 @param length length of the UID (integer) |
522 @return Uid object |
527 @return Uid object |
523 """ |
528 """ |
524 return Uid(self.readInteger(length+1)) |
529 return Uid(self.readInteger(length + 1)) |
525 |
530 |
526 def getSizedInteger(self, data, bytes): |
531 def getSizedInteger(self, data, bytes): |
527 """ |
532 """ |
528 Private method to read an integer of a specific size. |
533 Private method to read an integer of a specific size. |
529 |
534 |
542 result = unpack('>q', data)[0] |
547 result = unpack('>q', data)[0] |
543 else: |
548 else: |
544 raise InvalidPlistException("Encountered integer longer than 8 bytes.") |
549 raise InvalidPlistException("Encountered integer longer than 8 bytes.") |
545 return result |
550 return result |
546 |
551 |
|
552 |
547 class HashableWrapper(object): |
553 class HashableWrapper(object): |
548 """ |
554 """ |
549 Class wrapping a hashable value. |
555 Class wrapping a hashable value. |
550 """ |
556 """ |
551 def __init__(self, value): |
557 def __init__(self, value): |
552 self.value = value |
558 self.value = value |
|
559 |
553 def __repr__(self): |
560 def __repr__(self): |
554 return "<HashableWrapper: %s>" % [self.value] |
561 return "<HashableWrapper: %s>" % [self.value] |
555 |
562 |
|
563 |
556 class BoolWrapper(object): |
564 class BoolWrapper(object): |
557 """ |
565 """ |
558 Class wrapping a boolean value. |
566 Class wrapping a boolean value. |
559 """ |
567 """ |
560 def __init__(self, value): |
568 def __init__(self, value): |
561 self.value = value |
569 self.value = value |
|
570 |
562 def __repr__(self): |
571 def __repr__(self): |
563 return "<BoolWrapper: %s>" % self.value |
572 return "<BoolWrapper: %s>" % self.value |
|
573 |
564 |
574 |
565 class PlistWriter(object): |
575 class PlistWriter(object): |
566 """ |
576 """ |
567 Class implementing the plist writer. |
577 Class implementing the plist writer. |
568 """ |
578 """ |
638 |
648 |
639 @param root reference to the object to be written |
649 @param root reference to the object to be written |
640 """ |
650 """ |
641 output = self.header |
651 output = self.header |
642 wrapped_root = self.wrapRoot(root) |
652 wrapped_root = self.wrapRoot(root) |
643 should_reference_root = True#not isinstance(wrapped_root, HashableWrapper) |
653 should_reference_root = True # not isinstance(wrapped_root, HashableWrapper) |
644 self.computeOffsets(wrapped_root, asReference=should_reference_root, isRoot=True) |
654 self.computeOffsets(wrapped_root, asReference=should_reference_root, isRoot=True) |
645 self.trailer = self.trailer._replace( |
655 self.trailer = self.trailer._replace( |
646 **{'objectRefSize':self.intSize(len(self.computedUniques))}) |
656 **{'objectRefSize': self.intSize(len(self.computedUniques))}) |
647 (_, output) = self.writeObjectReference(wrapped_root, output) |
657 (_, output) = self.writeObjectReference(wrapped_root, output) |
648 output = self.writeObject(wrapped_root, output, setReferencePosition=True) |
658 output = self.writeObject(wrapped_root, output, setReferencePosition=True) |
649 |
659 |
650 # output size at this point is an upper bound on how big the |
660 # output size at this point is an upper bound on how big the |
651 # object reference offsets need to be. |
661 # object reference offsets need to be. |
652 self.trailer = self.trailer._replace(**{ |
662 self.trailer = self.trailer._replace(**{ |
653 'offsetSize':self.intSize(len(output)), |
663 'offsetSize': self.intSize(len(output)), |
654 'offsetCount':len(self.computedUniques), |
664 'offsetCount': len(self.computedUniques), |
655 'offsetTableOffset':len(output), |
665 'offsetTableOffset': len(output), |
656 'topLevelObjectNumber':0 |
666 'topLevelObjectNumber': 0 |
657 }) |
667 }) |
658 |
668 |
659 output = self.writeOffsetTable(output) |
669 output = self.writeOffsetTable(output) |
660 output += pack('!xxxxxxBBQQQ', *self.trailer) |
670 output += pack('!xxxxxxBBQQQ', *self.trailer) |
661 self.file.write(output) |
671 self.file.write(output) |
722 self.incrementByteCount('nullBytes') |
732 self.incrementByteCount('nullBytes') |
723 elif isinstance(obj, BoolWrapper): |
733 elif isinstance(obj, BoolWrapper): |
724 self.incrementByteCount('boolBytes') |
734 self.incrementByteCount('boolBytes') |
725 elif isinstance(obj, Uid): |
735 elif isinstance(obj, Uid): |
726 size = self.intSize(obj) |
736 size = self.intSize(obj) |
727 self.incrementByteCount('uidBytes', incr=1+size) |
737 self.incrementByteCount('uidBytes', incr=1 + size) |
728 elif isinstance(obj, int): |
738 elif isinstance(obj, int): |
729 size = self.intSize(obj) |
739 size = self.intSize(obj) |
730 self.incrementByteCount('intBytes', incr=1+size) |
740 self.incrementByteCount('intBytes', incr=1 + size) |
731 elif isinstance(obj, (float)): |
741 elif isinstance(obj, (float)): |
732 size = self.realSize(obj) |
742 size = self.realSize(obj) |
733 self.incrementByteCount('realBytes', incr=1+size) |
743 self.incrementByteCount('realBytes', incr=1 + size) |
734 elif isinstance(obj, datetime.datetime): |
744 elif isinstance(obj, datetime.datetime): |
735 self.incrementByteCount('dateBytes', incr=2) |
745 self.incrementByteCount('dateBytes', incr=2) |
736 elif isinstance(obj, Data): |
746 elif isinstance(obj, Data): |
737 size = proc_size(len(obj)) |
747 size = proc_size(len(obj)) |
738 self.incrementByteCount('dataBytes', incr=1+size) |
748 self.incrementByteCount('dataBytes', incr=1 + size) |
739 elif isinstance(obj, str): |
749 elif isinstance(obj, str): |
740 size = proc_size(len(obj)) |
750 size = proc_size(len(obj)) |
741 self.incrementByteCount('stringBytes', incr=1+size) |
751 self.incrementByteCount('stringBytes', incr=1 + size) |
742 elif isinstance(obj, HashableWrapper): |
752 elif isinstance(obj, HashableWrapper): |
743 obj = obj.value |
753 obj = obj.value |
744 if isinstance(obj, set): |
754 if isinstance(obj, set): |
745 size = proc_size(len(obj)) |
755 size = proc_size(len(obj)) |
746 self.incrementByteCount('setBytes', incr=1+size) |
756 self.incrementByteCount('setBytes', incr=1 + size) |
747 for value in obj: |
757 for value in obj: |
748 self.computeOffsets(value, asReference=True) |
758 self.computeOffsets(value, asReference=True) |
749 elif isinstance(obj, (list, tuple)): |
759 elif isinstance(obj, (list, tuple)): |
750 size = proc_size(len(obj)) |
760 size = proc_size(len(obj)) |
751 self.incrementByteCount('arrayBytes', incr=1+size) |
761 self.incrementByteCount('arrayBytes', incr=1 + size) |
752 for value in obj: |
762 for value in obj: |
753 self.computeOffsets(value, asReference=True) |
763 self.computeOffsets(value, asReference=True) |
754 elif isinstance(obj, dict): |
764 elif isinstance(obj, dict): |
755 size = proc_size(len(obj)) |
765 size = proc_size(len(obj)) |
756 self.incrementByteCount('dictBytes', incr=1+size) |
766 self.incrementByteCount('dictBytes', incr=1 + size) |
757 for key, value in obj.items(): |
767 for key, value in obj.items(): |
758 check_key(key) |
768 check_key(key) |
759 self.computeOffsets(key, asReference=True) |
769 self.computeOffsets(key, asReference=True) |
760 self.computeOffsets(value, asReference=True) |
770 self.computeOffsets(value, asReference=True) |
761 else: |
771 else: |
836 output += proc_variable_length(0b0100, len(obj)) |
846 output += proc_variable_length(0b0100, len(obj)) |
837 output += obj |
847 output += obj |
838 elif isinstance(obj, str): |
848 elif isinstance(obj, str): |
839 # Python 3 uses unicode strings only |
849 # Python 3 uses unicode strings only |
840 bytes = obj.encode('utf_16_be') |
850 bytes = obj.encode('utf_16_be') |
841 output += proc_variable_length(0b0110, len(bytes)/2) |
851 output += proc_variable_length(0b0110, len(bytes) / 2) |
842 output += bytes |
852 output += bytes |
843 elif isinstance(obj, HashableWrapper): |
853 elif isinstance(obj, HashableWrapper): |
844 obj = obj.value |
854 obj = obj.value |
845 if isinstance(obj, (set, list, tuple)): |
855 if isinstance(obj, (set, list, tuple)): |
846 if isinstance(obj, set): |
856 if isinstance(obj, set): |
937 |
947 |
938 @param obj integer object |
948 @param obj integer object |
939 @return number of bytes required (integer) |
949 @return number of bytes required (integer) |
940 """ |
950 """ |
941 # SIGNED |
951 # SIGNED |
942 if obj < 0: # Signed integer, always 8 bytes |
952 if obj < 0: # Signed integer, always 8 bytes |
943 return 8 |
953 return 8 |
944 # UNSIGNED |
954 # UNSIGNED |
945 elif obj <= 0xFF: # 1 byte |
955 elif obj <= 0xFF: # 1 byte |
946 return 1 |
956 return 1 |
947 elif obj <= 0xFFFF: # 2 bytes |
957 elif obj <= 0xFFFF: # 2 bytes |
948 return 2 |
958 return 2 |
949 elif obj <= 0xFFFFFFFF: # 4 bytes |
959 elif obj <= 0xFFFFFFFF: # 4 bytes |
950 return 4 |
960 return 4 |
951 # SIGNED |
961 # SIGNED |
952 # 0x7FFFFFFFFFFFFFFF is the max. |
962 # 0x7FFFFFFFFFFFFFFF is the max. |
953 elif obj <= 0x7FFFFFFFFFFFFFFF: # 8 bytes |
963 elif obj <= 0x7FFFFFFFFFFFFFFF: # 8 bytes |
954 return 8 |
964 return 8 |
955 else: |
965 else: |
956 raise InvalidPlistException( |
966 raise InvalidPlistException( |
957 "Core Foundation can't handle integers with size greater than 8 bytes.") |
967 "Core Foundation can't handle integers with size greater than 8 bytes.") |
958 |
968 |