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
# 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",
)
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()