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()
|