Created
September 17, 2022 21:13
-
-
Save nicolas17/87b4ec6edfd67daa3dc22d706de19806 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
# SPDX-FileCopyrightText: 2022 Nicolás Alvarez <[email protected]> | |
# | |
# SPDX-License-Identifier: MIT | |
import os | |
import sys | |
import json | |
import re | |
import plistlib | |
import requests | |
sess = requests.session() | |
url = sys.argv[1] | |
manifest_url = url[:url.rindex('/')] + '/BuildManifest.plist' | |
print("Loading manifest") | |
resp = sess.get(manifest_url) | |
assert resp.status_code == 200 | |
manifest = plistlib.loads(resp.content) | |
product_types = manifest['SupportedProductTypes'] | |
build = manifest['ProductBuildVersion'] | |
if "iProd99,1" in product_types: product_types.remove("iProd99,1") | |
if "ADP3,1" in product_types: product_types.remove("ADP3,1") | |
OS_MAP = [ | |
('iPod', 'iOS'), | |
('iPhone', 'iOS'), | |
('iPad', 'iPadOS'), | |
('AudioAccessory', 'audioOS'), | |
('AppleTV', 'tvOS'), | |
('MacBook', 'macOS'), | |
('Watch', 'watchOS'), | |
('iBridge', 'bridgeOS'), | |
] | |
for product_prefix, os_name in OS_MAP: | |
if any(prod.startswith(product_prefix) for prod in product_types): | |
break | |
else: | |
raise RuntimeError(f"Couldn't match product types to any known OS: {product_types}") | |
print(f"Matched product types to: {os_name}") | |
# JSON files are split into subdirectories per major version; | |
# figure out which subdir we have to look into | |
build_major = re.match('^\d+', build).group(0) | |
for subdir in os.listdir(f"iosFiles/{os_name}"): | |
if subdir.startswith(build_major+"x"): | |
version_subdir=subdir | |
break | |
else: | |
raise RuntimeError(f"Couldn't find a subdirectory in {os_name} for build {build} (major {build_major})") | |
# next, does the JSON file already exist? | |
json_filename = f"iosFiles/{os_name}/{version_subdir}/{build}.json" | |
print(f"JSON path is {json_filename}") | |
if os.path.isfile(json_filename): | |
# yep; load it | |
print(f"Reading from {json_filename}") | |
json_data = json.load(open(json_filename, "r")) | |
else: | |
# it doesn't, gotta start from scratch | |
print("JSON doesn't exist so we're starting from scratch") | |
json_data = { | |
"osStr": os_name, | |
"version": manifest["ProductVersion"] + " (FIXME)", | |
"build": build, | |
"released": "yyyy-mm-dd", | |
"beta": (re.match('.*[a-z]$', build) is not None), | |
"deviceMap": [] | |
} | |
for product in sorted(product_types): | |
if product not in json_data['deviceMap']: | |
json_data['deviceMap'].append(product) | |
if 'sources' not in json_data: | |
json_data['sources'] = [] | |
for source in json_data['sources']: | |
if set(source['deviceMap']) == set(product_types): | |
break | |
else: | |
print(f"no sources for {sorted(product_types)}, adding") | |
source = {} | |
source['deviceMap'] = sorted(product_types) | |
json_data['sources'].append(source) | |
source['type'] = 'ipsw' | |
HTTPS_PREFIX = 'https://updates.cdn-apple.com/' | |
HTTP_PREFIX = 'http://updates-http.cdn-apple.com/' | |
if url.startswith(HTTPS_PREFIX): | |
ipsw_https = url | |
ipsw_http = url.replace(HTTPS_PREFIX, HTTP_PREFIX) | |
elif url.startswith(HTTP_PREFIX): | |
ipsw_http = url | |
ipsw_https = url.replace(HTTP_PREFIX, HTTPS_PREFIX) | |
else: | |
raise RuntimeError(f"Unknown URL prefix on {repr(url)}") | |
source['links'] = [ | |
{ "url": ipsw_https, "preferred": True, "active": True }, | |
{ "url": ipsw_http, "preferred": False, "active": True } | |
] | |
if True: | |
print("Requesting ipsw URL") | |
resp = sess.head(ipsw_https) | |
if resp.status_code == 200: | |
source['size'] = int(resp.headers['content-length']) | |
if 'hashes' not in source: | |
source['hashes'] = {} | |
if 'x-amz-meta-digest-sh1' in resp.headers: | |
source['hashes']['sha1'] = resp.headers['x-amz-meta-digest-sh1'] | |
if 'x-amz-meta-digest-sha256' in resp.headers: | |
source['hashes']['sha2-256'] = resp.headers['x-amz-meta-digest-sha256'] | |
if source['hashes'] == {}: | |
# we didn't actually get any hashes, delete the empty dict | |
del source['hashes'] | |
else: | |
source['links'][0]['active'] = False | |
source['links'][1]['active'] = False | |
print(f"Writing to {json_filename}") | |
with open(json_filename, "w") as f: | |
json.dump(json_data, f, indent=4) | |
sess.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment