# 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-15-14-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()