generateGmpJson.py (14984B)
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/. 4 5 import argparse 6 import hashlib 7 import json 8 import logging 9 import re 10 from urllib.parse import urlparse, urlunparse 11 12 import requests 13 14 15 def fetch_url_for_cdms(cdms, urlParams): 16 any_version = None 17 for cdm in cdms: 18 if "fileName" in cdm: 19 cdm["fileUrl"] = cdm["fileName"].format_map(urlParams) 20 response = requests.get(cdm["fileUrl"], allow_redirects=False) 21 if response.status_code != 302: 22 raise Exception( 23 "{} unexpected status code {}".format( 24 cdm["target"], response.status_code 25 ) 26 ) 27 28 redirectUrl = response.headers["Location"] 29 parsedUrl = urlparse(redirectUrl) 30 if parsedUrl.scheme != "https": 31 raise Exception( 32 "{} expected https scheme '{}'".format(cdm["target"], redirectUrl) 33 ) 34 35 sanitizedUrl = urlunparse(( 36 parsedUrl.scheme, 37 parsedUrl.netloc, 38 parsedUrl.path, 39 None, 40 None, 41 None, 42 )) 43 44 # Note that here we modify the returned URL from the 45 # component update service because it returns a preferred 46 # server for the caller of the script. This may not match 47 # up with what the end users require. Google has requested 48 # that we instead replace these results with the 49 # edgedl.me.gvt1.com domain/path, which should be location 50 # agnostic. 51 normalizedUrl = re.sub( 52 r"https.+?release2", 53 "https://edgedl.me.gvt1.com/edgedl/release2", 54 sanitizedUrl, 55 ) 56 if not normalizedUrl: 57 raise Exception( 58 "{} cannot normalize '{}'".format(cdm["target"], sanitizedUrl) 59 ) 60 61 # Because some users are unable to resolve *.gvt1.com 62 # URLs, we supply an alternative based on www.google.com. 63 # This should resolve with success more frequently. 64 mirrorUrl = re.sub( 65 r"https.+?release2", 66 "https://www.google.com/dl/release2", 67 sanitizedUrl, 68 ) 69 70 version = re.search(r".*?_([\d]+\.[\d]+\.[\d]+\.[\d]+)/", sanitizedUrl) 71 if version is None: 72 raise Exception( 73 "{} cannot extract version '{}'".format(cdm["target"], sanitizedUrl) 74 ) 75 if any_version is None: 76 any_version = version.group(1) 77 elif version.group(1) != any_version: 78 raise Exception( 79 "{} version {} mismatch {}".format( 80 cdm["target"], version.group(1), any_version 81 ) 82 ) 83 cdm["fileName"] = normalizedUrl 84 if mirrorUrl and mirrorUrl != normalizedUrl: 85 cdm["fileNameMirror"] = mirrorUrl 86 return any_version 87 88 89 def fetch_data_for_cdms(cdms, urlParams): 90 for cdm in cdms: 91 if "fileName" in cdm: 92 cdm["fileUrl"] = cdm["fileName"].format_map(urlParams) 93 response = requests.get(cdm["fileUrl"]) 94 response.raise_for_status() 95 cdm["hashValue"] = hashlib.sha512(response.content).hexdigest() 96 if "fileNameMirror" in cdm: 97 cdm["mirrorUrl"] = cdm["fileNameMirror"].format_map(urlParams) 98 mirrorresponse = requests.get(cdm["mirrorUrl"]) 99 mirrorresponse.raise_for_status() 100 mirrorhash = hashlib.sha512(mirrorresponse.content).hexdigest() 101 if cdm["hashValue"] != mirrorhash: 102 raise Exception( 103 "Primary hash {} and mirror hash {} differ", 104 cdm["hashValue"], 105 mirrorhash, 106 ) 107 cdm["filesize"] = len(response.content) 108 if cdm["filesize"] == 0: 109 raise Exception("Empty response for {target}".format_map(cdm)) 110 111 112 def generate_json_for_cdms(cdms): 113 cdm_json = "" 114 for cdm in cdms: 115 if "alias" in cdm: 116 cdm_json += ( 117 ' "{target}": {{\n' 118 + ' "alias": "{alias}"\n' 119 + " }},\n" 120 ).format_map(cdm) 121 elif "mirrorUrl" in cdm: 122 cdm_json += ( 123 ' "{target}": {{\n' 124 + ' "fileUrl": "{fileUrl}",\n' 125 + ' "mirrorUrls": [\n' 126 + ' "{mirrorUrl}"\n' 127 + " ],\n" 128 + ' "filesize": {filesize},\n' 129 + ' "hashValue": "{hashValue}"\n' 130 + " }},\n" 131 ).format_map(cdm) 132 else: 133 cdm_json += ( 134 ' "{target}": {{\n' 135 + ' "fileUrl": "{fileUrl}",\n' 136 + ' "mirrorUrls": [],\n' 137 + ' "filesize": {filesize},\n' 138 + ' "hashValue": "{hashValue}"\n' 139 + " }},\n" 140 ).format_map(cdm) 141 return cdm_json[:-2] + "\n" 142 143 144 def calculate_gmpopenh264_json(version: str, version_hash: str, url_base: str) -> str: 145 # fmt: off 146 cdms = [ 147 {"target": "Darwin_aarch64-gcc3", "fileName": "{url_base}/openh264-macosx64-aarch64-{version}.zip"}, 148 {"target": "Darwin_x86_64-gcc3", "fileName": "{url_base}/openh264-macosx64-{version}.zip"}, 149 {"target": "Linux_aarch64-gcc3", "fileName": "{url_base}/openh264-linux64-aarch64-{version}.zip"}, 150 {"target": "Linux_x86-gcc3", "fileName": "{url_base}/openh264-linux32-{version}.zip"}, 151 {"target": "Linux_x86_64-gcc3", "fileName": "{url_base}/openh264-linux64-{version}.zip"}, 152 {"target": "Linux_x86_64-gcc3-asan", "alias": "Linux_x86_64-gcc3"}, 153 {"target": "WINNT_aarch64-msvc-aarch64", "fileName": "{url_base}/openh264-win64-aarch64-{version}.zip"}, 154 {"target": "WINNT_x86-msvc", "fileName": "{url_base}/openh264-win32-{version}.zip"}, 155 {"target": "WINNT_x86-msvc-x64", "alias": "WINNT_x86-msvc"}, 156 {"target": "WINNT_x86-msvc-x86", "alias": "WINNT_x86-msvc"}, 157 {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}/openh264-win64-{version}.zip"}, 158 {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"}, 159 {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"}, 160 ] 161 # fmt: on 162 try: 163 fetch_data_for_cdms(cdms, {"url_base": url_base, "version": version_hash}) 164 except Exception as e: 165 logging.error("calculate_gmpopenh264_json: could not create JSON due to: %s", e) 166 return "" 167 else: 168 return ( 169 "{\n" 170 + ' "hashFunction": "sha512",\n' 171 + f' "name": "OpenH264-{version}",\n' 172 + ' "schema_version": 1000,\n' 173 + ' "vendors": {\n' 174 + ' "gmp-gmpopenh264": {\n' 175 + ' "platforms": {\n' 176 + generate_json_for_cdms(cdms) 177 + " },\n" 178 + f' "version": "{version}"\n' 179 + " }\n" 180 + " }\n" 181 + "}" 182 ) 183 184 185 def calculate_widevinecdm_json(version: str, url_base: str) -> str: 186 # fmt: off 187 cdms = [ 188 {"target": "Darwin_aarch64-gcc3", "fileName": "{url_base}/{version}-mac-arm64.zip"}, 189 {"target": "Darwin_x86_64-gcc3", "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"}, 190 {"target": "Darwin_x86_64-gcc3-u-i386-x86_64", "fileName": "{url_base}/{version}-mac-x64.zip"}, 191 {"target": "Linux_x86_64-gcc3", "fileName": "{url_base}/{version}-linux-x64.zip"}, 192 {"target": "Linux_x86_64-gcc3-asan", "alias": "Linux_x86_64-gcc3"}, 193 {"target": "WINNT_aarch64-msvc-aarch64", "fileName": "{url_base}/{version}-win-arm64.zip"}, 194 {"target": "WINNT_x86-msvc", "fileName": "{url_base}/{version}-win-x86.zip"}, 195 {"target": "WINNT_x86-msvc-x64", "alias": "WINNT_x86-msvc"}, 196 {"target": "WINNT_x86-msvc-x86", "alias": "WINNT_x86-msvc"}, 197 {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}/{version}-win-x64.zip"}, 198 {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"}, 199 {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"}, 200 ] 201 # fmt: on 202 try: 203 fetch_data_for_cdms(cdms, {"url_base": url_base, "version": version}) 204 except Exception as e: 205 logging.error("calculate_widevinecdm_json: could not create JSON due to: %s", e) 206 return "" 207 else: 208 return ( 209 "{\n" 210 + ' "hashFunction": "sha512",\n' 211 + f' "name": "Widevine-{version}",\n' 212 + ' "schema_version": 1000,\n' 213 + ' "vendors": {\n' 214 + ' "gmp-widevinecdm": {\n' 215 + ' "platforms": {\n' 216 + generate_json_for_cdms(cdms) 217 + " },\n" 218 + f' "version": "{version}"\n' 219 + " }\n" 220 + " }\n" 221 + "}" 222 ) 223 224 225 def calculate_chrome_component_json( 226 name: str, altname: str, url_base: str, cdms 227 ) -> str: 228 try: 229 version = fetch_url_for_cdms(cdms, {"url_base": url_base}) 230 fetch_data_for_cdms(cdms, {}) 231 except Exception as e: 232 logging.error( 233 "calculate_chrome_component_json: could not create JSON due to: %s", e 234 ) 235 return "" 236 else: 237 return ( 238 "{\n" 239 + ' "hashFunction": "sha512",\n' 240 + f' "name": "{name}-{version}",\n' 241 + ' "schema_version": 1000,\n' 242 + ' "vendors": {\n' 243 + f' "gmp-{altname}": {{\n' 244 + ' "platforms": {\n' 245 + generate_json_for_cdms(cdms) 246 + " },\n" 247 + f' "version": "{version}"\n' 248 + " }\n" 249 + " }\n" 250 + "}" 251 ) 252 253 254 def calculate_widevinecdm_component_json(url_base: str) -> str: 255 # fmt: off 256 cdms = [ 257 {"target": "Darwin_aarch64-gcc3", "fileName": "{url_base}&os=mac&arch=arm64&os_arch=arm64"}, 258 {"target": "Darwin_x86_64-gcc3", "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"}, 259 {"target": "Darwin_x86_64-gcc3-u-i386-x86_64", "fileName": "{url_base}&os=mac&arch=x64&os_arch=x64"}, 260 {"target": "Linux_x86_64-gcc3", "fileName": "{url_base}&os=Linux&arch=x64&os_arch=x64"}, 261 {"target": "Linux_x86_64-gcc3-asan", "alias": "Linux_x86_64-gcc3"}, 262 {"target": "WINNT_aarch64-msvc-aarch64", "fileName": "{url_base}&os=win&arch=arm64&os_arch=arm64"}, 263 {"target": "WINNT_x86-msvc", "fileName": "{url_base}&os=win&arch=x86&os_arch=x86"}, 264 {"target": "WINNT_x86-msvc-x64", "alias": "WINNT_x86-msvc"}, 265 {"target": "WINNT_x86-msvc-x86", "alias": "WINNT_x86-msvc"}, 266 {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}&os=win&arch=x64&os_arch=x64"}, 267 {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"}, 268 {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"}, 269 ] 270 # fmt: on 271 return calculate_chrome_component_json( 272 "Widevine", 273 "widevinecdm", 274 url_base.format_map({"guid": "oimompecagnajdejgnnjijobebaeigek"}), 275 cdms, 276 ) 277 278 279 def calculate_widevinecdm_l1_component_json(url_base: str) -> str: 280 # fmt: off 281 cdms = [ 282 {"target": "WINNT_x86_64-msvc", "fileName": "{url_base}&os=win&arch=x64&os_arch=x64"}, 283 {"target": "WINNT_x86_64-msvc-x64", "alias": "WINNT_x86_64-msvc"}, 284 {"target": "WINNT_x86_64-msvc-x64-asan", "alias": "WINNT_x86_64-msvc"}, 285 ] 286 # fmt: on 287 return calculate_chrome_component_json( 288 "Widevine-L1", 289 "widevinecdm-l1", 290 url_base.format_map({"guid": "neifaoindggfcjicffkgpmnlppeffabd"}), 291 cdms, 292 ) 293 294 295 def main(): 296 examples = """examples: 297 python dom/media/tools/generateGmpJson.py widevine 4.10.2557.0 >toolkit/content/gmp-sources/widevinecdm.json 298 python dom/media/tools/generateGmpJson.py --url http://localhost:8080 openh264 2.3.1 0a48f4d2e9be2abb4fb01b4c3be83cf44ce91a6e 299 python dom/media/tools/generateGmpJson.py widevine_component""" 300 301 parser = argparse.ArgumentParser( 302 description="Generate JSON for GMP plugin updates", 303 epilog=examples, 304 formatter_class=argparse.RawDescriptionHelpFormatter, 305 ) 306 parser.add_argument( 307 "plugin", 308 help="which plugin: openh264, widevine, widevine_component, widevine_l1_component", 309 ) 310 parser.add_argument("version", help="version of plugin", nargs="?") 311 parser.add_argument("revision", help="revision hash of plugin", nargs="?") 312 parser.add_argument("--url", help="override base URL from which to fetch plugins") 313 parser.add_argument( 314 "--testrequest", 315 action="store_true", 316 help="request upcoming version for component update service", 317 ) 318 args = parser.parse_args() 319 320 if args.plugin == "openh264": 321 url_base = "https://ciscobinary.openh264.org" 322 if args.version is None or args.revision is None: 323 parser.error("openh264 requires version and revision") 324 elif args.plugin == "widevine": 325 url_base = "https://redirector.gvt1.com/edgedl/widevine-cdm" 326 if args.version is None: 327 parser.error("widevine requires version") 328 if args.revision is not None: 329 parser.error("widevine cannot use revision") 330 elif args.plugin in ("widevine_component", "widevine_l1_component"): 331 url_base = "https://update.googleapis.com/service/update2/crx?response=redirect&x=id%3D{guid}%26uc&acceptformat=crx3&updaterversion=999" 332 if args.testrequest: 333 url_base += "&testrequest=1" 334 if args.version is not None or args.revision is not None: 335 parser.error("chrome component cannot use version or revision") 336 else: 337 parser.error("plugin not recognized") 338 339 if args.url is not None: 340 url_base = args.url 341 342 if url_base[-1] == "/": 343 url_base = url_base[:-1] 344 345 if args.plugin == "openh264": 346 json_result = calculate_gmpopenh264_json(args.version, args.revision, url_base) 347 elif args.plugin == "widevine": 348 json_result = calculate_widevinecdm_json(args.version, url_base) 349 elif args.plugin == "widevine_component": 350 json_result = calculate_widevinecdm_component_json(url_base) 351 elif args.plugin == "widevine_l1_component": 352 json_result = calculate_widevinecdm_l1_component_json(url_base) 353 354 try: 355 json.loads(json_result) 356 except json.JSONDecodeError as e: 357 logging.error("invalid JSON produced: %s", e) 358 else: 359 print(json_result) 360 361 362 main()