1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 from urllib
.request
import urlopen
11 from xml
.dom
import minidom
14 from mozpack
.macpkg
import Pbzx
, uncpio
, unxar
17 def get_english(dict, default
=None):
18 english
= dict.get("English")
20 english
= dict.get("en", default
)
24 def get_content_at(url
):
25 ssl_context
= ssl
.create_default_context(cafile
=certifi
.where())
26 f
= urlopen(url
, context
=ssl_context
)
30 def get_plist_at(url
):
31 return plistlib
.loads(get_content_at(url
))
34 def show_package_content(url
, digest
=None, size
=None):
35 package
= get_content_at(url
)
36 if size
is not None and len(package
) != size
:
37 print(f
"Package does not match size given in catalog: {url}", file=sys
.stderr
)
39 # Ideally we'd check the digest, but it's not md5, sha1 or sha256...
40 # if digest is not None and hashlib.???(package).hexdigest() != digest:
41 # print(f"Package does not match digest given in catalog: {url}", file=sys.stderr)
43 for name
, content
in unxar(BytesIO(package
)):
45 for path
, _
, __
in uncpio(Pbzx(content
)):
47 print(path
.decode("utf-8"))
50 def show_product_info(product
, package_id
=None):
51 # An alternative here would be to look at the MetadataURLs in
52 # product["Packages"], but going with Distributions allows to
53 # only do one request.
54 dist
= get_english(product
.get("Distributions"))
55 data
= get_content_at(dist
)
56 dom
= minidom
.parseString(data
.decode("utf-8"))
57 for pkg_ref
in dom
.getElementsByTagName("pkg-ref"):
58 if pkg_ref
.childNodes
:
59 if pkg_ref
.hasAttribute("packageIdentifier"):
60 id = pkg_ref
.attributes
["packageIdentifier"].value
62 id = pkg_ref
.attributes
["id"].value
64 if package_id
and package_id
!= id:
67 for child
in pkg_ref
.childNodes
:
68 if child
.nodeType
!= minidom
.Node
.TEXT_NODE
:
70 for p
in product
["Packages"]:
71 if p
["URL"].endswith("/" + child
.data
):
74 p
["URL"], p
.get("Digest"), p
.get("Size")
80 def show_products(products
, filter=None):
81 for key
, product
in products
.items():
82 metadata_url
= product
.get("ServerMetadataURL", "")
83 if metadata_url
and (not filter or filter in metadata_url
):
84 metadata
= get_plist_at(metadata_url
)
85 localization
= get_english(metadata
.get("localization", {}), {})
86 title
= localization
.get("title", None)
87 version
= metadata
.get("CFBundleShortVersionString", None)
88 print(key
, title
, version
)
92 parser
= argparse
.ArgumentParser()
95 help="URL of the catalog",
96 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",
99 "--filter", help="Only show entries with metadata url matching the filter"
102 "what", nargs
="?", help="Show packages information about the given entry"
104 args
= parser
.parse_args()
106 data
= get_plist_at(args
.catalog
)
107 products
= data
["Products"]
112 "Cannot use --filter when showing verbose information about an entry",
116 product_id
, _
, package_id
= args
.what
.partition("/")
117 show_product_info(products
[product_id
], package_id
)
119 show_products(products
, args
.filter)
122 if __name__
== "__main__":