summaryrefslogtreecommitdiffstats
path: root/build/macosx/catalog.py
blob: e3d937ffafb5df090a388ee8342fd82a089bd767 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import argparse
import plistlib
import ssl
import sys
from io import BytesIO
from urllib.request import urlopen
from xml.dom import minidom

import certifi
from mozpack.macpkg import Pbzx, uncpio, unxar


def get_english(dict, default=None):
    english = dict.get("English")
    if english is None:
        english = dict.get("en", default)
    return english


def get_content_at(url):
    ssl_context = ssl.create_default_context(cafile=certifi.where())
    f = urlopen(url, context=ssl_context)
    return f.read()


def get_plist_at(url):
    return plistlib.loads(get_content_at(url))


def show_package_content(url, digest=None, size=None):
    package = get_content_at(url)
    if size is not None and len(package) != size:
        print(f"Package does not match size given in catalog: {url}", file=sys.stderr)
        sys.exit(1)
    # Ideally we'd check the digest, but it's not md5, sha1 or sha256...
    # if digest is not None and hashlib.???(package).hexdigest() != digest:
    #     print(f"Package does not match digest given in catalog: {url}", file=sys.stderr)
    #     sys.exit(1)
    for name, content in unxar(BytesIO(package)):
        if name == "Payload":
            for path, _, __ in uncpio(Pbzx(content)):
                if path:
                    print(path.decode("utf-8"))


def show_product_info(product, package_id=None):
    # An alternative here would be to look at the MetadataURLs in
    # product["Packages"], but going with Distributions allows to
    # only do one request.
    dist = get_english(product.get("Distributions"))
    data = get_content_at(dist)
    dom = minidom.parseString(data.decode("utf-8"))
    for pkg_ref in dom.getElementsByTagName("pkg-ref"):
        if pkg_ref.childNodes:
            if pkg_ref.hasAttribute("packageIdentifier"):
                id = pkg_ref.attributes["packageIdentifier"].value
            else:
                id = pkg_ref.attributes["id"].value

            if package_id and package_id != id:
                continue

            for child in pkg_ref.childNodes:
                if child.nodeType != minidom.Node.TEXT_NODE:
                    continue
                for p in product["Packages"]:
                    if p["URL"].endswith("/" + child.data):
                        if package_id:
                            show_package_content(
                                p["URL"], p.get("Digest"), p.get("Size")
                            )
                        else:
                            print(id, p["URL"])


def show_products(products, filter=None):
    for key, product in products.items():
        metadata_url = product.get("ServerMetadataURL", "")
        if metadata_url and (not filter or filter in metadata_url):
            metadata = get_plist_at(metadata_url)
            localization = get_english(metadata.get("localization", {}), {})
            title = localization.get("title", None)
            version = metadata.get("CFBundleShortVersionString", None)
            print(key, title, version)


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--catalog",
        help="URL of the catalog",
        default="https://swscan.apple.com/content/catalogs/others/index-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog",
    )
    parser.add_argument(
        "--filter", help="Only show entries with metadata url matching the filter"
    )
    parser.add_argument(
        "what", nargs="?", help="Show packages information about the given entry"
    )
    args = parser.parse_args()

    data = get_plist_at(args.catalog)
    products = data["Products"]

    if args.what:
        if args.filter:
            print(
                "Cannot use --filter when showing verbose information about an entry",
                file=sys.stderr,
            )
            sys.exit(1)
        product_id, _, package_id = args.what.partition("/")
        show_product_info(products[product_id], package_id)
    else:
        show_products(products, args.filter)


if __name__ == "__main__":
    main()