Ejemplo n.º 1
0
def _convert_CFUID_to_UID(plist, use_plistlib=False):
    ''' For converting XML plists to binary, UIDs which are represented
        as strings 'CF$UID' must be translated to actual UIDs.
    '''
    if isinstance(plist, dict):
        for k, v in plist.items():
            if isinstance(v, dict):
                num = v.get('CF$UID', None)
                if (num is None) or (not isinstance(num, int)):
                    _convert_CFUID_to_UID(v, use_plistlib)
                else:
                    if use_plistlib:
                        plist[k] = plistlib.UID(num)
                    else:
                        plist[k] = biplist.Uid(num)
            elif isinstance(v, list):
                _convert_CFUID_to_UID(v, use_plistlib)
    else:  # list
        for index, v in enumerate(plist):
            if isinstance(v, dict):
                num = v.get('CF$UID', None)
                if (num is None) or (not isinstance(num, int)):
                    _convert_CFUID_to_UID(v, use_plistlib)
                else:
                    if use_plistlib:
                        plist[index] = plistlib.UID(num)
                    else:
                        plist[index] = biplist.Uid(num)
            elif isinstance(v, list):
                _convert_CFUID_to_UID(v, use_plistlib)
Ejemplo n.º 2
0
    def archive(self, obj) -> plistlib.UID:
        "Add the encoded form of obj to the archive, returning the UID of obj."

        if obj is None:
            return NULL_UID

        # the ref_map allows us to avoid infinite recursion caused by
        # cycles in the object graph by functioning as a sort of promise
        ref = self.ref_map.get(id(obj))
        if ref:
            return ref

        index = plistlib.UID(len(self.objects))
        self.ref_map[id(obj)] = index

        cls = obj.__class__
        if cls in Archive.primitive_types:
            self.objects.append(obj)
            return index

        archive_obj: Dict[str, object] = {}
        self.objects.append(archive_obj)
        self.encode_top_level(obj, archive_obj)

        return index
Ejemplo n.º 3
0
    def uid_for_archiver(self, archiver: type) -> plistlib.UID:
        """
        Ensure the class definition for the archiver is included in the arcive.

        Non-primitive objects are encoded as a dictionary of key-value pairs;
        there is always a $class key, which has a UID value...the UID is itself
        a pointer/index which points to the definition of the class (which is
        also in the archive).

        This method makes sure that all the metadata is included in the archive
        exactly once (no duplicates class metadata).
        """

        val = self.class_map.get(archiver)
        if val:
            return val

        val = plistlib.UID(len(self.objects))
        self.class_map[archiver] = val

        # TODO: this is where we might need to include the full class ancestry;
        #       though the open source code from apple does not appear to check
        self.objects.append({'$classes': [archiver], '$classname': archiver})

        return val
def create_color_entry(colors):
    rgb = rgb_dict_to_rgb_bytes(colors)
    plist = {
        "$version":
        100000,
        "$objects": [
            "$null", {
                "NSRGB": rgb,
                "NSColorSpace": 2,
                "$class": plistlib.UID(2)
            }, {
                "$classname": "NSColor",
                "$classes": ["NSColor", "NSObject"]
            }
        ],
        "$archiver":
        "NSKeyedArchiver",
        "$top": {
            "root": plistlib.UID(1)
        }
    }

    return plistlib.dumps(plist, fmt=plistlib.FMT_BINARY, sort_keys=False)
Ejemplo n.º 5
0
    def to_bytes(self) -> bytes:
        "Generate the archive and return it as a bytes blob"

        # avoid regenerating
        if len(self.objects) == 1:
            self.archive(self.input)

        d = {
            '$archiver': 'NSKeyedArchiver',
            '$version': NSKeyedArchiveVersion,
            '$objects': self.objects,
            '$top': {
                'root': plistlib.UID(1)
            }
        }
        return plistlib.dumps(d, fmt=plistlib.FMT_BINARY)  # pylint: disable=no-member
Ejemplo n.º 6
0
from typing import Mapping, Dict

from bpylist.archive_types import timestamp, NSMutableData

if sys.version_info < (3, 8, 0):
    from . import _plistlib as plistlib
else:
    import plistlib  # type: ignore

# The magic number which Cocoa uses as an implementation version.
# I don' think there were 99_999 previous implementations, I think
# Apple just likes to store a lot of zeros
NSKeyedArchiveVersion = 100_000

# Cached for convenience
NULL_UID = plistlib.UID(0)


def unarchive(plist: bytes) -> object:
    "Unpack an NSKeyedArchived byte blob into a more useful object tree."
    return Unarchive(plist).top_object()


def unarchive_file(path: str) -> object:
    """Loads an archive from a file path."""
    with open(path, 'rb') as fd:
        return unarchive(fd.read())


def archive(obj: object) -> bytes:
    "Pack an object tree into an NSKeyedArchived blob."
Ejemplo n.º 7
0
def add_supported_app(
    app_config: Dict[str, Any],
    source_app: AppInfo,
    target_apps: Sequence[AppInfo],
) -> None:
    def fix_uuids(
        copy_from: int,
        copy_to: int,
        previous_length: int,
        key: Any,
        value: Any,
    ) -> plistlib.UID:
        if isinstance(value, plistlib.UID):
            value.data = compute_new_uid(copy_from, copy_to, previous_length,
                                         value.data)
        return None

    activation_group_cond = app_config["BTTActivationGroupCondition"]
    # Condition is a base64-encoded binary plist
    parsed_plist = plistlib.loads(
        base64.urlsafe_b64decode(activation_group_cond))
    # Note to future self: I have no clue how plist's work - just what I gathered
    # from reading and reversing the existing file
    #
    # figure out the right operators

    try:
        center = parsed_plist["$objects"].index(source_app.bundle_name)
        use_bundle = True
    except:
        center = parsed_plist["$objects"].index(source_app.app_name)
        use_bundle = False

    # start searching back from the located bundle / app name
    # keep track of any index with a forward ref to our bundle/app name
    # or a forward ref to another item that has one to it (transitive)
    idxs_to_search = [center]
    for i in range(center, 0, -1):
        obj_at_pos = parsed_plist["$objects"][i]
        if not isinstance(obj_at_pos, dict):
            continue
        for k, v in obj_at_pos.items():
            # if the curre
            if isinstance(v, plistlib.UID):
                if v.data in idxs_to_search:
                    # Keep track of the new forward ref
                    idxs_to_search.append(i)
    copy_from = idxs_to_search[-1]

    # The first item in our predicate will have forward refs to all the required bits
    # including the predicate
    # transitively search for it
    copy_to = max((v.data
                   for k, v in parsed_plist["$objects"][copy_from].items()
                   if isinstance(v, plistlib.UID)))
    # search for potential forward refs from copy_to onwards
    while True:
        if not isinstance(parsed_plist["$objects"][copy_to], dict):
            break
        new_max = max((v.data
                       for k, v in parsed_plist["$objects"][copy_to].items()
                       if isinstance(v, plistlib.UID)))
        if new_max <= copy_to:
            break
        copy_to = new_max

    root_levels_to_add = []
    for target_app in target_apps:
        new_items = copy.deepcopy(parsed_plist["$objects"][copy_from:(copy_to +
                                                                      1)])
        previous_length = len(parsed_plist["$objects"])
        print(f"Adding {target_app} - starting at ID {previous_length}")
        root_levels_to_add.append(previous_length)
        fix_callable = partial(fix_uuids, copy_from, copy_to, previous_length)
        for item_pos, item in enumerate(new_items):
            if isinstance(item, plistlib.UID):
                fix_callable(None, item)
            elif isinstance(item, (list, dict)):
                recursive_modify_collection(item, fix_callable)
            elif isinstance(item, str):
                if use_bundle and item == source_app.bundle_name:
                    new_items[item_pos] = target_app.bundle_name
                elif not use_bundle and item == source_app.app_name:
                    new_items[item_pos] = target_app.app_name
        parsed_plist["$objects"].extend(new_items)

    # find top level pointer to all the apps
    for item_pos, item in enumerate(parsed_plist["$objects"]):
        if not isinstance(item, dict):
            continue
        if "NS.objects" not in item:
            continue
        # search for our minimum range - i.e. the first item that denoted the app entry we copied
        if plistlib.UID(copy_from) in item["NS.objects"]:
            for new_root in root_levels_to_add:
                item["NS.objects"].append(plistlib.UID(new_root))
            break
    else:
        raise ValueError(
            "Could not append new app - could not locate root level list")

    print("Added to root tree - dumping plist and we'll be done")
    app_config["BTTActivationGroupCondition"] = base64.standard_b64encode(
        plistlib.dumps(
            parsed_plist,
            fmt=plistlib.FMT_BINARY,
            sort_keys=True,
        )).decode("ascii")