diff --git a/jc/parsers/plist.py b/jc/parsers/plist.py index 3578ac59..ae3119fe 100644 --- a/jc/parsers/plist.py +++ b/jc/parsers/plist.py @@ -2,6 +2,10 @@ Converts binary and XML PLIST files. +Binary values are converted into an ASCII hex representation. + +Datetime objects are converted into Unix epoch timestamps and ISO strings. + Usage (cli): $ cat file.plist | jc --plist @@ -31,6 +35,8 @@ Examples: """ from typing import List, Dict, Union import plistlib +import binascii +from datetime import datetime import jc.utils @@ -61,6 +67,57 @@ def _process(proc_data: Dict) -> Dict: return proc_data +def _b2a(byte_string: bytes) -> str: + """Convert a byte string to a colon-delimited hex ascii string""" + # need try/except since seperator was only introduced in python 3.8. + # provides compatibility for python 3.6 and 3.7. + try: + return binascii.hexlify(byte_string, ':').decode('utf-8') + except TypeError: + hex_string = binascii.hexlify(byte_string).decode('utf-8') + colon_seperated = ':'.join(hex_string[i:i+2] for i in range(0, len(hex_string), 2)) + return colon_seperated + + +def _fix_objects(obj): + """ + Recursively traverse the nested dictionary or list and convert objects + into JSON serializable types. + """ + if isinstance(obj, dict): + for k, v in obj.copy().items(): + + if isinstance(v, datetime): + iso = v.isoformat() + v = int(round(v.timestamp())) + obj.update({k: v, f'{k}_iso': iso}) + continue + + if isinstance(v, bytes): + v = _b2a(v) + obj.update({k: v}) + continue + + if isinstance(v, dict): + obj.update({k: _fix_objects(v)}) + continue + + if isinstance(v, list): + newlist =[] + for i in v: + newlist.append(_fix_objects(i)) + obj.update({k: newlist}) + continue + + if isinstance(obj, list): + new_list = [] + for i in obj: + new_list.append(_fix_objects(i)) + obj = new_list + + return obj + + def parse( data: Union[str, bytes], raw: bool = False, @@ -89,5 +146,6 @@ def parse( if jc.utils.has_data(data): raw_output = plistlib.loads(data) + raw_output = _fix_objects(raw_output) return raw_output if raw else _process(raw_output) diff --git a/tests/fixtures/generic/plist-alltypes.plist b/tests/fixtures/generic/plist-alltypes.plist new file mode 100644 index 00000000..fccd690e --- /dev/null +++ b/tests/fixtures/generic/plist-alltypes.plist @@ -0,0 +1,49 @@ + + + + + aDate + 2022-07-31T16:34:30Z + aDict + + aFalseValue + + aThirdString + Mässig, Maß + aTrueValue + + anotherString + <hello & hi there!> + + aFloat + 0.1 + aList + + A + B + 12 + 32.1 + + 1 + 2 + 3 + + + aString + Doodah + anInt + 728 + someData + + PGJpbmFyeSBndW5rPg== + + someMoreData + + PGxvdHMgb2YgYmluYXJ5IGd1bms+PGxvdHMgb2YgYmluYXJ5IGd1bms+PGxvdHMgb2Yg + YmluYXJ5IGd1bms+PGxvdHMgb2YgYmluYXJ5IGd1bms+PGxvdHMgb2YgYmluYXJ5IGd1 + bms+PGxvdHMgb2YgYmluYXJ5IGd1bms+PGxvdHMgb2YgYmluYXJ5IGd1bms+PGxvdHMg + b2YgYmluYXJ5IGd1bms+PGxvdHMgb2YgYmluYXJ5IGd1bms+PGxvdHMgb2YgYmluYXJ5 + IGd1bms+ + + +