Source code
Revision control
Copy as Markdown
Other Tools
# 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
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",
)
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()